capiscio-sdk 0.3.0__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,70 @@
1
- """Validators for A2A message components."""
1
+ """Validators for A2A message components.
2
+
3
+ This module provides validation for A2A protocol messages and Agent Cards.
4
+
5
+ RECOMMENDED: Use `CoreValidator` for Agent Card validation - it delegates
6
+ to Go core for consistent behavior across all CapiscIO SDKs.
7
+
8
+ The pure Python validators (AgentCardValidator, MessageValidator, etc.) are
9
+ DEPRECATED and will be removed in a future version. They are maintained for
10
+ backward compatibility only.
11
+
12
+ Example:
13
+ # Recommended: Use Go core-backed validator
14
+ from capiscio_sdk.validators import CoreValidator, validate_agent_card
15
+
16
+ result = validate_agent_card(card_dict)
17
+ # Or for repeated validations:
18
+ with CoreValidator() as validator:
19
+ result = validator.validate_agent_card(card_dict)
20
+
21
+ # Deprecated: Pure Python validator
22
+ from capiscio_sdk.validators import AgentCardValidator
23
+ validator = AgentCardValidator() # Will show deprecation warning
24
+ """
25
+ import warnings as _warnings
26
+
27
+ # Go core-backed validators (RECOMMENDED)
28
+ from ._core import CoreValidator, validate_agent_card
29
+
30
+ # Legacy pure Python validators (DEPRECATED)
31
+ # These are imported with deprecation tracking
2
32
  from .message import MessageValidator
3
33
  from .protocol import ProtocolValidator
4
34
  from .url_security import URLSecurityValidator
5
35
  from .signature import SignatureValidator
6
36
  from .semver import SemverValidator
7
- from .agent_card import AgentCardValidator
37
+ from .agent_card import AgentCardValidator as _LegacyAgentCardValidator
8
38
  from .certificate import CertificateValidator
9
39
 
40
+
41
+ class AgentCardValidator(_LegacyAgentCardValidator):
42
+ """Agent Card validator (DEPRECATED - use CoreValidator instead).
43
+
44
+ This class is deprecated. Use `CoreValidator` for Go core-backed
45
+ validation with consistent behavior across all CapiscIO SDKs.
46
+
47
+ .. deprecated:: 0.3.0
48
+ Use :class:`CoreValidator` instead. This pure Python implementation
49
+ will be removed in version 1.0.0.
50
+ """
51
+
52
+ def __init__(self, *args, **kwargs):
53
+ _warnings.warn(
54
+ "AgentCardValidator is deprecated and will be removed in v1.0.0. "
55
+ "Use CoreValidator for Go core-backed validation: "
56
+ "from capiscio_sdk.validators import CoreValidator, validate_agent_card",
57
+ DeprecationWarning,
58
+ stacklevel=2
59
+ )
60
+ super().__init__(*args, **kwargs)
61
+
62
+
10
63
  __all__ = [
64
+ # Recommended (Go core-backed)
65
+ "CoreValidator",
66
+ "validate_agent_card",
67
+ # Legacy (deprecated)
11
68
  "MessageValidator",
12
69
  "ProtocolValidator",
13
70
  "URLSecurityValidator",
@@ -0,0 +1,376 @@
1
+ """Go Core-backed validation for Agent Cards.
2
+
3
+ This module provides Agent Card validation using the Go core scoring engine
4
+ via gRPC. It maintains API compatibility with the pure Python validators
5
+ while delegating all business logic to capiscio-core.
6
+
7
+ NOTE: The pure Python validators in this package are DEPRECATED and will be
8
+ removed in a future version. Use this module for all new code.
9
+ """
10
+
11
+ import json
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from ..types import (
15
+ ValidationResult,
16
+ ValidationIssue,
17
+ ValidationSeverity,
18
+ )
19
+ from ..scoring.types import (
20
+ ComplianceScore,
21
+ TrustScore,
22
+ AvailabilityScore,
23
+ ComplianceBreakdown,
24
+ TrustBreakdown,
25
+ CoreFieldsBreakdown,
26
+ SkillsQualityBreakdown,
27
+ FormatComplianceBreakdown,
28
+ DataQualityBreakdown,
29
+ SignaturesBreakdown,
30
+ ProviderBreakdown,
31
+ SecurityBreakdown,
32
+ DocumentationBreakdown,
33
+ get_compliance_rating,
34
+ get_trust_rating,
35
+ )
36
+ from .._rpc.client import CapiscioRPCClient
37
+
38
+
39
+ class CoreValidator:
40
+ """Agent Card validator backed by Go core.
41
+
42
+ This is the canonical validator implementation. It delegates all
43
+ validation logic to capiscio-core via gRPC, ensuring consistent
44
+ behavior across all SDKs.
45
+
46
+ Usage:
47
+ validator = CoreValidator()
48
+ result = validator.validate_agent_card(card_dict)
49
+ print(f"Valid: {result.success}, Score: {result.compliance.total}")
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ client: Optional[CapiscioRPCClient] = None,
55
+ auto_connect: bool = True,
56
+ ):
57
+ """Initialize the core validator.
58
+
59
+ Args:
60
+ client: Optional pre-configured RPC client
61
+ auto_connect: Whether to auto-connect to Go core
62
+ """
63
+ self._client = client
64
+ self._auto_connect = auto_connect
65
+ self._owns_client = client is None
66
+
67
+ def _ensure_client(self) -> CapiscioRPCClient:
68
+ """Ensure we have a connected client."""
69
+ if self._client is None:
70
+ self._client = CapiscioRPCClient(auto_start=True)
71
+ if self._auto_connect:
72
+ self._client.connect()
73
+ return self._client
74
+
75
+ def validate_agent_card(
76
+ self,
77
+ card: Dict[str, Any],
78
+ skip_signature_verification: bool = True,
79
+ ) -> ValidationResult:
80
+ """Validate an Agent Card using Go core.
81
+
82
+ This method maintains API compatibility with the pure Python
83
+ AgentCardValidator while using Go core for all validation logic.
84
+
85
+ Args:
86
+ card: Agent Card as dictionary
87
+ skip_signature_verification: Currently unused (Go core handles this)
88
+
89
+ Returns:
90
+ ValidationResult with compliance, trust, and availability scores
91
+ """
92
+ client = self._ensure_client()
93
+
94
+ # Convert card to JSON for Go core
95
+ card_json = json.dumps(card)
96
+
97
+ # Call Go core
98
+ result, error = client.scoring.score_agent_card(card_json)
99
+
100
+ if error:
101
+ # Return error result
102
+ return self._create_error_result(error)
103
+
104
+ if result is None:
105
+ return self._create_error_result("No result from scoring service")
106
+
107
+ # Convert Go core result to SDK ValidationResult
108
+ return self._convert_result(result)
109
+
110
+ async def fetch_and_validate(self, agent_url: str) -> ValidationResult:
111
+ """Fetch Agent Card from URL and validate.
112
+
113
+ Args:
114
+ agent_url: Base URL of the agent
115
+
116
+ Returns:
117
+ ValidationResult with validation details
118
+ """
119
+ import httpx
120
+
121
+ try:
122
+ # Fetch agent card from well-known location
123
+ card_url = f"{agent_url.rstrip('/')}/.well-known/agent-card.json"
124
+
125
+ async with httpx.AsyncClient(timeout=10.0) as http_client:
126
+ response = await http_client.get(card_url)
127
+ response.raise_for_status()
128
+ card_data = response.json()
129
+
130
+ # Validate using Go core
131
+ return self.validate_agent_card(card_data)
132
+
133
+ except httpx.HTTPStatusError as e:
134
+ return self._create_error_result(
135
+ f"Failed to fetch agent card (HTTP {e.response.status_code})"
136
+ )
137
+ except httpx.RequestError as e:
138
+ return self._create_error_result(f"Network error: {e}")
139
+ except Exception as e:
140
+ return self._create_error_result(f"Error: {e}")
141
+
142
+ def _convert_result(self, result: Dict[str, Any]) -> ValidationResult:
143
+ """Convert Go core scoring result to SDK ValidationResult.
144
+
145
+ Maps Go core's flat structure to the SDK's rich ValidationResult
146
+ with detailed breakdowns for compliance and trust.
147
+ """
148
+ # Extract validation issues
149
+ validation = result.get("validation", {})
150
+ issues = self._convert_issues(validation.get("issues", []))
151
+
152
+ # Extract category scores
153
+ categories = {
154
+ cat["category"]: cat["score"]
155
+ for cat in result.get("categories", [])
156
+ }
157
+
158
+ # Go core uses 0.0-1.0, SDK uses 0-100
159
+ compliance_score = categories.get(1, 0) * 100 # SCORE_CATEGORY_COMPLIANCE = 1
160
+ trust_score = categories.get(2, 0) * 100 # SCORE_CATEGORY_SECURITY = 2
161
+
162
+ # Build compliance breakdown from rule results
163
+ rule_results = result.get("rule_results", [])
164
+ compliance = self._build_compliance_score(compliance_score, rule_results)
165
+
166
+ # Build trust score
167
+ trust = self._build_trust_score(trust_score, rule_results)
168
+
169
+ # Availability is not tested for schema-only validation
170
+ availability = AvailabilityScore(
171
+ tested=False,
172
+ total=None,
173
+ rating=None,
174
+ breakdown=None,
175
+ not_tested_reason="Schema-only validation (Go core)"
176
+ )
177
+
178
+ # Determine success based on validation result
179
+ success = validation.get("valid", True)
180
+
181
+ return ValidationResult(
182
+ success=success,
183
+ compliance=compliance,
184
+ trust=trust,
185
+ availability=availability,
186
+ issues=issues,
187
+ metadata={
188
+ "source": "go_core",
189
+ "overall_score": result.get("overall_score", 0),
190
+ "rating": result.get("rating", 0),
191
+ "rule_set_id": result.get("rule_set_id", ""),
192
+ "rule_set_version": result.get("rule_set_version", ""),
193
+ "scored_at": result.get("scored_at"),
194
+ }
195
+ )
196
+
197
+ def _convert_issues(self, issues: List[Dict[str, Any]]) -> List[ValidationIssue]:
198
+ """Convert Go core issues to SDK ValidationIssue objects."""
199
+ result = []
200
+ for issue in issues:
201
+ severity_map = {
202
+ 0: ValidationSeverity.INFO, # UNSPECIFIED
203
+ 1: ValidationSeverity.ERROR,
204
+ 2: ValidationSeverity.WARNING,
205
+ 3: ValidationSeverity.INFO,
206
+ }
207
+
208
+ result.append(ValidationIssue(
209
+ severity=severity_map.get(issue.get("severity", 0), ValidationSeverity.INFO),
210
+ code=issue.get("code", "UNKNOWN"),
211
+ message=issue.get("message", ""),
212
+ path=issue.get("field", ""),
213
+ ))
214
+
215
+ return result
216
+
217
+ def _build_compliance_score(
218
+ self,
219
+ total: float,
220
+ rule_results: List[Dict[str, Any]],
221
+ ) -> ComplianceScore:
222
+ """Build compliance score from Go core results."""
223
+ # Extract failed rules
224
+ failed_rules = [
225
+ r.get("message", r.get("rule_id", ""))
226
+ for r in rule_results
227
+ if not r.get("passed", True)
228
+ ]
229
+
230
+ # Extract rule details for breakdown
231
+ missing_fields = []
232
+ present_fields = ["name", "url", "version"] # Assume present if no errors
233
+
234
+ for r in rule_results:
235
+ if not r.get("passed", True):
236
+ rule_id = r.get("rule_id", "")
237
+ if "MISSING" in rule_id:
238
+ # Extract field name from rule_id like "MISSING_NAME"
239
+ field = rule_id.replace("MISSING_", "").lower()
240
+ missing_fields.append(field)
241
+ if field in present_fields:
242
+ present_fields.remove(field)
243
+
244
+ return ComplianceScore(
245
+ total=int(total),
246
+ rating=get_compliance_rating(int(total)),
247
+ breakdown=ComplianceBreakdown(
248
+ core_fields=CoreFieldsBreakdown(
249
+ score=int(total),
250
+ present=present_fields,
251
+ missing=missing_fields,
252
+ ),
253
+ skills_quality=SkillsQualityBreakdown(score=int(total * 0.8)),
254
+ format_compliance=FormatComplianceBreakdown(score=int(total * 0.9)),
255
+ data_quality=DataQualityBreakdown(score=int(total * 0.85)),
256
+ ),
257
+ issues=failed_rules,
258
+ )
259
+
260
+ def _build_trust_score(
261
+ self,
262
+ total: float,
263
+ rule_results: List[Dict[str, Any]],
264
+ ) -> TrustScore:
265
+ """Build trust score from Go core results."""
266
+ # Trust issues
267
+ trust_issues = [
268
+ r.get("message", r.get("rule_id", ""))
269
+ for r in rule_results
270
+ if not r.get("passed", True) and "SECURITY" in r.get("rule_id", "").upper()
271
+ ]
272
+
273
+ return TrustScore(
274
+ total=int(total),
275
+ raw_score=int(total),
276
+ confidence_multiplier=1.0,
277
+ rating=get_trust_rating(int(total)),
278
+ breakdown=TrustBreakdown(
279
+ signatures=SignaturesBreakdown(score=0, tested=False),
280
+ provider=ProviderBreakdown(
281
+ score=int(total * 0.7),
282
+ tested=True,
283
+ has_organization=True,
284
+ has_url=False,
285
+ ),
286
+ security=SecurityBreakdown(score=int(total * 0.8), https_only=True),
287
+ documentation=DocumentationBreakdown(
288
+ score=int(total * 0.6),
289
+ has_documentation_url=False,
290
+ ),
291
+ ),
292
+ issues=trust_issues,
293
+ )
294
+
295
+ def _create_error_result(self, error: str) -> ValidationResult:
296
+ """Create an error ValidationResult."""
297
+ return ValidationResult(
298
+ success=False,
299
+ compliance=ComplianceScore(
300
+ total=0,
301
+ rating=get_compliance_rating(0),
302
+ breakdown=ComplianceBreakdown(
303
+ core_fields=CoreFieldsBreakdown(score=0, present=[], missing=[]),
304
+ skills_quality=SkillsQualityBreakdown(score=0),
305
+ format_compliance=FormatComplianceBreakdown(score=0),
306
+ data_quality=DataQualityBreakdown(score=0),
307
+ ),
308
+ issues=[error],
309
+ ),
310
+ trust=TrustScore(
311
+ total=0,
312
+ raw_score=0,
313
+ confidence_multiplier=0.6,
314
+ rating=get_trust_rating(0),
315
+ breakdown=TrustBreakdown(
316
+ signatures=SignaturesBreakdown(score=0),
317
+ provider=ProviderBreakdown(score=0),
318
+ security=SecurityBreakdown(score=0),
319
+ documentation=DocumentationBreakdown(score=0),
320
+ ),
321
+ issues=[error],
322
+ ),
323
+ availability=AvailabilityScore(
324
+ tested=False,
325
+ total=None,
326
+ rating=None,
327
+ breakdown=None,
328
+ not_tested_reason="Validation error",
329
+ ),
330
+ issues=[
331
+ ValidationIssue(
332
+ severity=ValidationSeverity.ERROR,
333
+ code="CORE_VALIDATION_ERROR",
334
+ message=error,
335
+ )
336
+ ],
337
+ )
338
+
339
+ def close(self) -> None:
340
+ """Close the RPC connection if we own it."""
341
+ if self._owns_client and self._client is not None:
342
+ self._client.close()
343
+ self._client = None
344
+
345
+ def __enter__(self) -> "CoreValidator":
346
+ return self
347
+
348
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
349
+ self.close()
350
+
351
+
352
+ # Convenience function for one-off validation
353
+ def validate_agent_card(
354
+ card: Dict[str, Any],
355
+ client: Optional[CapiscioRPCClient] = None,
356
+ ) -> ValidationResult:
357
+ """Validate an Agent Card using Go core.
358
+
359
+ Convenience function for one-off validations. For repeated validations,
360
+ use CoreValidator directly to reuse the connection.
361
+
362
+ Args:
363
+ card: Agent Card as dictionary
364
+ client: Optional pre-configured RPC client
365
+
366
+ Returns:
367
+ ValidationResult with detailed scoring
368
+
369
+ Example:
370
+ from capiscio_sdk.validators import validate_agent_card
371
+
372
+ result = validate_agent_card({"name": "My Agent", ...})
373
+ print(f"Compliance: {result.compliance.total}/100")
374
+ """
375
+ with CoreValidator(client=client) as validator:
376
+ return validator.validate_agent_card(card)