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.
- capiscio_sdk/__init__.py +67 -1
- capiscio_sdk/_rpc/__init__.py +7 -0
- capiscio_sdk/_rpc/client.py +1321 -0
- capiscio_sdk/_rpc/gen/__init__.py +1 -0
- capiscio_sdk/_rpc/process.py +232 -0
- capiscio_sdk/badge.py +737 -0
- capiscio_sdk/badge_keeper.py +304 -0
- capiscio_sdk/dv.py +296 -0
- capiscio_sdk/executor.py +5 -5
- capiscio_sdk/integrations/fastapi.py +3 -2
- capiscio_sdk/scoring/__init__.py +73 -3
- capiscio_sdk/simple_guard.py +196 -204
- capiscio_sdk/validators/__init__.py +59 -2
- capiscio_sdk/validators/_core.py +376 -0
- capiscio_sdk-2.3.0.dist-info/METADATA +532 -0
- {capiscio_sdk-0.3.0.dist-info → capiscio_sdk-2.3.0.dist-info}/RECORD +18 -10
- {capiscio_sdk-0.3.0.dist-info → capiscio_sdk-2.3.0.dist-info}/WHEEL +1 -1
- capiscio_sdk-0.3.0.dist-info/METADATA +0 -126
- {capiscio_sdk-0.3.0.dist-info → capiscio_sdk-2.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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)
|