capiscio-sdk 0.2.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 +42 -0
- capiscio_sdk/config.py +114 -0
- capiscio_sdk/errors.py +69 -0
- capiscio_sdk/executor.py +216 -0
- capiscio_sdk/infrastructure/__init__.py +5 -0
- capiscio_sdk/infrastructure/cache.py +73 -0
- capiscio_sdk/infrastructure/rate_limiter.py +110 -0
- capiscio_sdk/py.typed +0 -0
- capiscio_sdk/scoring/__init__.py +42 -0
- capiscio_sdk/scoring/availability.py +299 -0
- capiscio_sdk/scoring/compliance.py +314 -0
- capiscio_sdk/scoring/trust.py +340 -0
- capiscio_sdk/scoring/types.py +353 -0
- capiscio_sdk/types.py +234 -0
- capiscio_sdk/validators/__init__.py +18 -0
- capiscio_sdk/validators/agent_card.py +444 -0
- capiscio_sdk/validators/certificate.py +384 -0
- capiscio_sdk/validators/message.py +360 -0
- capiscio_sdk/validators/protocol.py +162 -0
- capiscio_sdk/validators/semver.py +202 -0
- capiscio_sdk/validators/signature.py +234 -0
- capiscio_sdk/validators/url_security.py +269 -0
- capiscio_sdk-0.2.0.dist-info/METADATA +221 -0
- capiscio_sdk-0.2.0.dist-info/RECORD +26 -0
- capiscio_sdk-0.2.0.dist-info/WHEEL +4 -0
- capiscio_sdk-0.2.0.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Trust scorer for security and authenticity signals.
|
|
2
|
+
|
|
3
|
+
Calculates trust score (0-100) based on:
|
|
4
|
+
- Cryptographic signatures (40 points)
|
|
5
|
+
- Provider information (25 points)
|
|
6
|
+
- Security configuration (20 points)
|
|
7
|
+
- Documentation and transparency (15 points)
|
|
8
|
+
|
|
9
|
+
Applies confidence multiplier based on signature state:
|
|
10
|
+
- Valid signature: 1.0x (full confidence)
|
|
11
|
+
- No signature: 0.6x (unverified claims)
|
|
12
|
+
- Invalid signature: 0.4x (active distrust)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, List
|
|
16
|
+
from ..types import ValidationIssue
|
|
17
|
+
from .types import (
|
|
18
|
+
TrustScore,
|
|
19
|
+
TrustBreakdown,
|
|
20
|
+
SignaturesBreakdown,
|
|
21
|
+
ProviderBreakdown,
|
|
22
|
+
SecurityBreakdown,
|
|
23
|
+
DocumentationBreakdown,
|
|
24
|
+
get_trust_rating,
|
|
25
|
+
get_trust_confidence_multiplier,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TrustScorer:
|
|
30
|
+
"""Calculates trust scores for security and authenticity signals."""
|
|
31
|
+
|
|
32
|
+
def score_agent_card(
|
|
33
|
+
self,
|
|
34
|
+
card_data: Dict[str, Any],
|
|
35
|
+
issues: List[ValidationIssue],
|
|
36
|
+
skip_signature_verification: bool = False
|
|
37
|
+
) -> TrustScore:
|
|
38
|
+
"""Calculate trust score for an agent card.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
card_data: Agent card data dictionary
|
|
42
|
+
issues: List of validation issues found
|
|
43
|
+
skip_signature_verification: Whether signature verification was skipped
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
TrustScore with detailed breakdown
|
|
47
|
+
"""
|
|
48
|
+
# Calculate each component
|
|
49
|
+
signatures = self._score_signatures(card_data, issues, skip_signature_verification)
|
|
50
|
+
provider = self._score_provider(card_data, issues)
|
|
51
|
+
security = self._score_security(card_data, issues)
|
|
52
|
+
documentation = self._score_documentation(card_data)
|
|
53
|
+
|
|
54
|
+
# Calculate raw score
|
|
55
|
+
raw_score = (
|
|
56
|
+
signatures.score +
|
|
57
|
+
provider.score +
|
|
58
|
+
security.score +
|
|
59
|
+
documentation.score
|
|
60
|
+
)
|
|
61
|
+
raw_score = max(0, min(100, raw_score))
|
|
62
|
+
|
|
63
|
+
# Apply confidence multiplier
|
|
64
|
+
confidence_multiplier = get_trust_confidence_multiplier(
|
|
65
|
+
has_valid_signature=signatures.has_valid_signature,
|
|
66
|
+
has_invalid_signature=signatures.has_invalid_signature
|
|
67
|
+
)
|
|
68
|
+
total = int(raw_score * confidence_multiplier)
|
|
69
|
+
|
|
70
|
+
# Create breakdown
|
|
71
|
+
breakdown = TrustBreakdown(
|
|
72
|
+
signatures=signatures,
|
|
73
|
+
provider=provider,
|
|
74
|
+
security=security,
|
|
75
|
+
documentation=documentation
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Extract issue messages
|
|
79
|
+
issue_messages = [
|
|
80
|
+
issue.message for issue in issues
|
|
81
|
+
if self._is_trust_issue(issue)
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
return TrustScore(
|
|
85
|
+
total=total,
|
|
86
|
+
raw_score=raw_score,
|
|
87
|
+
confidence_multiplier=confidence_multiplier,
|
|
88
|
+
rating=get_trust_rating(total),
|
|
89
|
+
breakdown=breakdown,
|
|
90
|
+
issues=issue_messages,
|
|
91
|
+
partial_validation=skip_signature_verification
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _score_signatures(
|
|
95
|
+
self,
|
|
96
|
+
card_data: Dict[str, Any],
|
|
97
|
+
issues: List[ValidationIssue],
|
|
98
|
+
skip_verification: bool
|
|
99
|
+
) -> SignaturesBreakdown:
|
|
100
|
+
"""Score cryptographic signatures (40 points).
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
card_data: Agent card data
|
|
104
|
+
issues: Validation issues
|
|
105
|
+
skip_verification: Whether verification was skipped
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
SignaturesBreakdown with signature metrics
|
|
109
|
+
"""
|
|
110
|
+
score = 0
|
|
111
|
+
tested = not skip_verification
|
|
112
|
+
|
|
113
|
+
has_valid = False
|
|
114
|
+
multiple_sigs = False
|
|
115
|
+
covers_all = False
|
|
116
|
+
is_recent = False
|
|
117
|
+
has_invalid = False
|
|
118
|
+
has_expired = False
|
|
119
|
+
|
|
120
|
+
if tested:
|
|
121
|
+
# Check for valid signature (25 points)
|
|
122
|
+
has_valid = not self._has_issue_code(issues, [
|
|
123
|
+
"SIGNATURE_VERIFICATION_FAILED",
|
|
124
|
+
"MISSING_SIGNATURE"
|
|
125
|
+
])
|
|
126
|
+
if has_valid:
|
|
127
|
+
score += 25
|
|
128
|
+
|
|
129
|
+
# Check for invalid signature
|
|
130
|
+
has_invalid = self._has_issue_code(issues, "SIGNATURE_VERIFICATION_FAILED")
|
|
131
|
+
|
|
132
|
+
# Check for expired signature
|
|
133
|
+
has_expired = self._has_issue_code(issues, "SIGNATURE_EXPIRED")
|
|
134
|
+
|
|
135
|
+
# Check for multiple signatures (5 points)
|
|
136
|
+
signatures = card_data.get("signatures", [])
|
|
137
|
+
if isinstance(signatures, list) and len(signatures) > 1:
|
|
138
|
+
multiple_sigs = True
|
|
139
|
+
score += 5
|
|
140
|
+
|
|
141
|
+
# Check if signature covers all fields (5 points)
|
|
142
|
+
# In practice, this would verify the JWS payload includes all card fields
|
|
143
|
+
if has_valid and not self._has_issue_code(issues, "INCOMPLETE_SIGNATURE_COVERAGE"):
|
|
144
|
+
covers_all = True
|
|
145
|
+
score += 5
|
|
146
|
+
|
|
147
|
+
# Check if signature is recent (5 points)
|
|
148
|
+
# Signatures less than 90 days old
|
|
149
|
+
if has_valid and not has_expired:
|
|
150
|
+
is_recent = True
|
|
151
|
+
score += 5
|
|
152
|
+
|
|
153
|
+
return SignaturesBreakdown(
|
|
154
|
+
score=score,
|
|
155
|
+
max_score=40,
|
|
156
|
+
tested=tested,
|
|
157
|
+
has_valid_signature=has_valid,
|
|
158
|
+
multiple_signatures=multiple_sigs,
|
|
159
|
+
covers_all_fields=covers_all,
|
|
160
|
+
is_recent=is_recent,
|
|
161
|
+
has_invalid_signature=has_invalid,
|
|
162
|
+
has_expired_signature=has_expired
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _score_provider(
|
|
166
|
+
self,
|
|
167
|
+
card_data: Dict[str, Any],
|
|
168
|
+
issues: List[ValidationIssue]
|
|
169
|
+
) -> ProviderBreakdown:
|
|
170
|
+
"""Score provider information (25 points).
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
card_data: Agent card data
|
|
174
|
+
issues: Validation issues
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
ProviderBreakdown with provider metrics
|
|
178
|
+
"""
|
|
179
|
+
score = 0
|
|
180
|
+
provider = card_data.get("provider", {})
|
|
181
|
+
|
|
182
|
+
# Ensure provider is a dict (defensive coding for validation errors)
|
|
183
|
+
if not isinstance(provider, dict):
|
|
184
|
+
provider = {}
|
|
185
|
+
|
|
186
|
+
# Check for organization (10 points)
|
|
187
|
+
has_org = bool(provider.get("organization"))
|
|
188
|
+
if has_org:
|
|
189
|
+
score += 10
|
|
190
|
+
|
|
191
|
+
# Check for provider URL (10 points)
|
|
192
|
+
has_url = bool(provider.get("url"))
|
|
193
|
+
if has_url:
|
|
194
|
+
score += 10
|
|
195
|
+
|
|
196
|
+
# Check if URL is reachable (5 points)
|
|
197
|
+
# This would require network test, so we check if no related errors
|
|
198
|
+
url_reachable = None
|
|
199
|
+
if has_url and not self._has_issue_code(issues, ["PROVIDER_URL_UNREACHABLE"]):
|
|
200
|
+
url_reachable = True
|
|
201
|
+
score += 5
|
|
202
|
+
elif has_url:
|
|
203
|
+
url_reachable = False
|
|
204
|
+
|
|
205
|
+
return ProviderBreakdown(
|
|
206
|
+
score=score,
|
|
207
|
+
max_score=25,
|
|
208
|
+
tested=True,
|
|
209
|
+
has_organization=has_org,
|
|
210
|
+
has_url=has_url,
|
|
211
|
+
url_reachable=url_reachable
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _score_security(
|
|
215
|
+
self,
|
|
216
|
+
card_data: Dict[str, Any],
|
|
217
|
+
issues: List[ValidationIssue]
|
|
218
|
+
) -> SecurityBreakdown:
|
|
219
|
+
"""Score security configuration (20 points).
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
card_data: Agent card data
|
|
223
|
+
issues: Validation issues
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
SecurityBreakdown with security metrics
|
|
227
|
+
"""
|
|
228
|
+
score = 0
|
|
229
|
+
|
|
230
|
+
# Check HTTPS only (10 points)
|
|
231
|
+
has_http = self._has_issue_code(issues, ["INSECURE_URL", "HTTP_URL_FOUND"])
|
|
232
|
+
https_only = not has_http
|
|
233
|
+
if https_only:
|
|
234
|
+
score += 10
|
|
235
|
+
|
|
236
|
+
# Check for security schemes (5 points)
|
|
237
|
+
capabilities = card_data.get("capabilities", {})
|
|
238
|
+
# Ensure capabilities is a dict (defensive coding for validation errors)
|
|
239
|
+
if not isinstance(capabilities, dict):
|
|
240
|
+
capabilities = {}
|
|
241
|
+
security_schemes = capabilities.get("securitySchemes", [])
|
|
242
|
+
has_security_schemes = bool(security_schemes)
|
|
243
|
+
if has_security_schemes:
|
|
244
|
+
score += 5
|
|
245
|
+
|
|
246
|
+
# Check for strong auth (5 points)
|
|
247
|
+
# OAuth2, API Key, or other authentication
|
|
248
|
+
has_strong_auth = False
|
|
249
|
+
if has_security_schemes:
|
|
250
|
+
for scheme in security_schemes:
|
|
251
|
+
scheme_type = scheme.get("type", "").lower()
|
|
252
|
+
if scheme_type in ["oauth2", "apikey", "http"]:
|
|
253
|
+
has_strong_auth = True
|
|
254
|
+
break
|
|
255
|
+
if has_strong_auth:
|
|
256
|
+
score += 5
|
|
257
|
+
|
|
258
|
+
return SecurityBreakdown(
|
|
259
|
+
score=score,
|
|
260
|
+
max_score=20,
|
|
261
|
+
https_only=https_only,
|
|
262
|
+
has_security_schemes=has_security_schemes,
|
|
263
|
+
has_strong_auth=has_strong_auth,
|
|
264
|
+
has_http_urls=has_http
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def _score_documentation(self, card_data: Dict[str, Any]) -> DocumentationBreakdown:
|
|
268
|
+
"""Score documentation and transparency (15 points).
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
card_data: Agent card data
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
DocumentationBreakdown with documentation metrics
|
|
275
|
+
"""
|
|
276
|
+
score = 0
|
|
277
|
+
|
|
278
|
+
# Check for documentation URL (5 points)
|
|
279
|
+
has_docs = bool(card_data.get("documentationUrl"))
|
|
280
|
+
if has_docs:
|
|
281
|
+
score += 5
|
|
282
|
+
|
|
283
|
+
# Check for terms of service (5 points)
|
|
284
|
+
has_tos = bool(card_data.get("termsOfService"))
|
|
285
|
+
if has_tos:
|
|
286
|
+
score += 5
|
|
287
|
+
|
|
288
|
+
# Check for privacy policy (5 points)
|
|
289
|
+
has_privacy = bool(card_data.get("privacyPolicy"))
|
|
290
|
+
if has_privacy:
|
|
291
|
+
score += 5
|
|
292
|
+
|
|
293
|
+
return DocumentationBreakdown(
|
|
294
|
+
score=score,
|
|
295
|
+
max_score=15,
|
|
296
|
+
has_documentation_url=has_docs,
|
|
297
|
+
has_terms_of_service=has_tos,
|
|
298
|
+
has_privacy_policy=has_privacy
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def _is_trust_issue(self, issue: ValidationIssue) -> bool:
|
|
302
|
+
"""Check if issue is trust-related.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
issue: Validation issue to check
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
True if trust-related
|
|
309
|
+
"""
|
|
310
|
+
trust_codes = {
|
|
311
|
+
"SIGNATURE_VERIFICATION_FAILED",
|
|
312
|
+
"MISSING_SIGNATURE",
|
|
313
|
+
"SIGNATURE_EXPIRED",
|
|
314
|
+
"INCOMPLETE_SIGNATURE_COVERAGE",
|
|
315
|
+
"INSECURE_URL",
|
|
316
|
+
"HTTP_URL_FOUND",
|
|
317
|
+
"PROVIDER_URL_UNREACHABLE",
|
|
318
|
+
"SSRF_RISK",
|
|
319
|
+
"PRIVATE_IP",
|
|
320
|
+
}
|
|
321
|
+
return issue.code in trust_codes
|
|
322
|
+
|
|
323
|
+
def _has_issue_code(
|
|
324
|
+
self,
|
|
325
|
+
issues: List[ValidationIssue],
|
|
326
|
+
codes: str | List[str]
|
|
327
|
+
) -> bool:
|
|
328
|
+
"""Check if any issue has given code(s).
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
issues: List of validation issues
|
|
332
|
+
codes: Single code or list of codes to check
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
True if any issue matches
|
|
336
|
+
"""
|
|
337
|
+
if isinstance(codes, str):
|
|
338
|
+
codes = [codes]
|
|
339
|
+
code_set = set(codes)
|
|
340
|
+
return any(issue.code in code_set for issue in issues)
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""Type definitions for multi-dimensional scoring system.
|
|
2
|
+
|
|
3
|
+
Defines the three core score types (Compliance, Trust, Availability),
|
|
4
|
+
their breakdown structures, rating enums, and helper functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ============================================================================
|
|
13
|
+
# Rating Enums
|
|
14
|
+
# ============================================================================
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ComplianceRating(str, Enum):
|
|
18
|
+
"""Compliance score rating levels."""
|
|
19
|
+
PERFECT = "Perfect"
|
|
20
|
+
EXCELLENT = "Excellent"
|
|
21
|
+
GOOD = "Good"
|
|
22
|
+
FAIR = "Fair"
|
|
23
|
+
POOR = "Poor"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TrustRating(str, Enum):
|
|
27
|
+
"""Trust score rating levels."""
|
|
28
|
+
HIGHLY_TRUSTED = "Highly Trusted"
|
|
29
|
+
TRUSTED = "Trusted"
|
|
30
|
+
MODERATE_TRUST = "Moderate Trust"
|
|
31
|
+
LOW_TRUST = "Low Trust"
|
|
32
|
+
UNTRUSTED = "Untrusted"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AvailabilityRating(str, Enum):
|
|
36
|
+
"""Availability score rating levels."""
|
|
37
|
+
FULLY_AVAILABLE = "Fully Available"
|
|
38
|
+
AVAILABLE = "Available"
|
|
39
|
+
DEGRADED = "Degraded"
|
|
40
|
+
UNSTABLE = "Unstable"
|
|
41
|
+
UNAVAILABLE = "Unavailable"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ============================================================================
|
|
45
|
+
# Breakdown Structures
|
|
46
|
+
# ============================================================================
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class CoreFieldsBreakdown:
|
|
51
|
+
"""Breakdown for core required fields scoring."""
|
|
52
|
+
score: int
|
|
53
|
+
max_score: int = 60
|
|
54
|
+
present: List[str] = field(default_factory=list)
|
|
55
|
+
missing: List[str] = field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class SkillsQualityBreakdown:
|
|
60
|
+
"""Breakdown for skills quality scoring."""
|
|
61
|
+
score: int
|
|
62
|
+
max_score: int = 20
|
|
63
|
+
skills_present: bool = False
|
|
64
|
+
all_skills_have_required_fields: bool = False
|
|
65
|
+
all_skills_have_tags: bool = False
|
|
66
|
+
issue_count: int = 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class FormatComplianceBreakdown:
|
|
71
|
+
"""Breakdown for format compliance scoring."""
|
|
72
|
+
score: int
|
|
73
|
+
max_score: int = 15
|
|
74
|
+
valid_semver: bool = False
|
|
75
|
+
valid_protocol_version: bool = False
|
|
76
|
+
valid_url: bool = False
|
|
77
|
+
valid_transports: bool = False
|
|
78
|
+
valid_mime_types: bool = False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class DataQualityBreakdown:
|
|
83
|
+
"""Breakdown for data quality scoring."""
|
|
84
|
+
score: int
|
|
85
|
+
max_score: int = 5
|
|
86
|
+
no_duplicate_skill_ids: bool = False
|
|
87
|
+
field_lengths_valid: bool = False
|
|
88
|
+
no_ssrf_risks: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class ComplianceBreakdown:
|
|
93
|
+
"""Complete compliance score breakdown (100 points total)."""
|
|
94
|
+
core_fields: CoreFieldsBreakdown
|
|
95
|
+
skills_quality: SkillsQualityBreakdown
|
|
96
|
+
format_compliance: FormatComplianceBreakdown
|
|
97
|
+
data_quality: DataQualityBreakdown
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class SignaturesBreakdown:
|
|
102
|
+
"""Breakdown for signature validation scoring."""
|
|
103
|
+
score: int
|
|
104
|
+
max_score: int = 40
|
|
105
|
+
tested: bool = False
|
|
106
|
+
has_valid_signature: bool = False
|
|
107
|
+
multiple_signatures: bool = False
|
|
108
|
+
covers_all_fields: bool = False
|
|
109
|
+
is_recent: bool = False
|
|
110
|
+
has_invalid_signature: bool = False
|
|
111
|
+
has_expired_signature: bool = False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class ProviderBreakdown:
|
|
116
|
+
"""Breakdown for provider information scoring."""
|
|
117
|
+
score: int
|
|
118
|
+
max_score: int = 25
|
|
119
|
+
tested: bool = False
|
|
120
|
+
has_organization: bool = False
|
|
121
|
+
has_url: bool = False
|
|
122
|
+
url_reachable: Optional[bool] = None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class SecurityBreakdown:
|
|
127
|
+
"""Breakdown for security configuration scoring."""
|
|
128
|
+
score: int
|
|
129
|
+
max_score: int = 20
|
|
130
|
+
https_only: bool = False
|
|
131
|
+
has_security_schemes: bool = False
|
|
132
|
+
has_strong_auth: bool = False
|
|
133
|
+
has_http_urls: bool = False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class DocumentationBreakdown:
|
|
138
|
+
"""Breakdown for documentation and transparency scoring."""
|
|
139
|
+
score: int
|
|
140
|
+
max_score: int = 15
|
|
141
|
+
has_documentation_url: bool = False
|
|
142
|
+
has_terms_of_service: bool = False
|
|
143
|
+
has_privacy_policy: bool = False
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class TrustBreakdown:
|
|
148
|
+
"""Complete trust score breakdown (100 points before multiplier)."""
|
|
149
|
+
signatures: SignaturesBreakdown
|
|
150
|
+
provider: ProviderBreakdown
|
|
151
|
+
security: SecurityBreakdown
|
|
152
|
+
documentation: DocumentationBreakdown
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class PrimaryEndpointBreakdown:
|
|
157
|
+
"""Breakdown for primary endpoint scoring."""
|
|
158
|
+
score: int
|
|
159
|
+
max_score: int = 50
|
|
160
|
+
responds: bool = False
|
|
161
|
+
response_time: Optional[float] = None
|
|
162
|
+
has_cors: Optional[bool] = None
|
|
163
|
+
valid_tls: Optional[bool] = None
|
|
164
|
+
errors: List[str] = field(default_factory=list)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class TransportSupportBreakdown:
|
|
169
|
+
"""Breakdown for transport protocol support scoring."""
|
|
170
|
+
score: int
|
|
171
|
+
max_score: int = 30
|
|
172
|
+
preferred_transport_works: bool = False
|
|
173
|
+
additional_interfaces_working: int = 0
|
|
174
|
+
additional_interfaces_failed: int = 0
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class ResponseQualityBreakdown:
|
|
179
|
+
"""Breakdown for response quality scoring."""
|
|
180
|
+
score: int
|
|
181
|
+
max_score: int = 20
|
|
182
|
+
valid_structure: bool = False
|
|
183
|
+
proper_content_type: bool = False
|
|
184
|
+
proper_error_handling: bool = False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass
|
|
188
|
+
class AvailabilityBreakdown:
|
|
189
|
+
"""Complete availability score breakdown (100 points total)."""
|
|
190
|
+
primary_endpoint: PrimaryEndpointBreakdown
|
|
191
|
+
transport_support: TransportSupportBreakdown
|
|
192
|
+
response_quality: ResponseQualityBreakdown
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ============================================================================
|
|
196
|
+
# Core Score Types
|
|
197
|
+
# ============================================================================
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class ComplianceScore:
|
|
202
|
+
"""Compliance score (0-100): Measures A2A specification adherence.
|
|
203
|
+
|
|
204
|
+
Always calculated consistently regardless of validation flags.
|
|
205
|
+
"""
|
|
206
|
+
total: int
|
|
207
|
+
rating: ComplianceRating
|
|
208
|
+
breakdown: ComplianceBreakdown
|
|
209
|
+
issues: List[str] = field(default_factory=list)
|
|
210
|
+
|
|
211
|
+
def __post_init__(self) -> None:
|
|
212
|
+
"""Validate score is in range."""
|
|
213
|
+
assert 0 <= self.total <= 100, f"Invalid compliance score: {self.total}"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@dataclass
|
|
217
|
+
class TrustScore:
|
|
218
|
+
"""Trust score (0-100): Measures security and authenticity signals.
|
|
219
|
+
|
|
220
|
+
Includes confidence multiplier based on signature presence.
|
|
221
|
+
"""
|
|
222
|
+
total: int # After confidence multiplier
|
|
223
|
+
raw_score: int # Before multiplier
|
|
224
|
+
confidence_multiplier: float # 1.0x, 0.6x, or 0.4x
|
|
225
|
+
rating: TrustRating
|
|
226
|
+
breakdown: TrustBreakdown
|
|
227
|
+
issues: List[str] = field(default_factory=list)
|
|
228
|
+
partial_validation: bool = False
|
|
229
|
+
|
|
230
|
+
def __post_init__(self) -> None:
|
|
231
|
+
"""Validate score is in range."""
|
|
232
|
+
assert 0 <= self.total <= 100, f"Invalid trust score: {self.total}"
|
|
233
|
+
assert 0 <= self.raw_score <= 100, f"Invalid raw trust score: {self.raw_score}"
|
|
234
|
+
assert self.confidence_multiplier in (0.4, 0.6, 1.0), \
|
|
235
|
+
f"Invalid confidence multiplier: {self.confidence_multiplier}"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class AvailabilityScore:
|
|
240
|
+
"""Availability score (0-100): Measures operational readiness.
|
|
241
|
+
|
|
242
|
+
Only calculated when network tests are enabled (not schema-only mode).
|
|
243
|
+
"""
|
|
244
|
+
total: Optional[int] # None if not tested
|
|
245
|
+
rating: Optional[AvailabilityRating]
|
|
246
|
+
breakdown: Optional[AvailabilityBreakdown]
|
|
247
|
+
issues: List[str] = field(default_factory=list)
|
|
248
|
+
tested: bool = False
|
|
249
|
+
not_tested_reason: Optional[str] = None
|
|
250
|
+
|
|
251
|
+
def __post_init__(self) -> None:
|
|
252
|
+
"""Validate score is in range if present."""
|
|
253
|
+
if self.total is not None:
|
|
254
|
+
assert 0 <= self.total <= 100, f"Invalid availability score: {self.total}"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ============================================================================
|
|
258
|
+
# Context & Helpers
|
|
259
|
+
# ============================================================================
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass
|
|
263
|
+
class ScoringContext:
|
|
264
|
+
"""Context about what validation was performed."""
|
|
265
|
+
schema_only: bool = False
|
|
266
|
+
skip_signature_verification: bool = False
|
|
267
|
+
test_live: bool = False
|
|
268
|
+
strict_mode: bool = False
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ============================================================================
|
|
272
|
+
# Rating Helper Functions
|
|
273
|
+
# ============================================================================
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def get_compliance_rating(score: int) -> ComplianceRating:
|
|
277
|
+
"""Get compliance rating based on score.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
score: Compliance score (0-100)
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
ComplianceRating enum value
|
|
284
|
+
"""
|
|
285
|
+
if score == 100:
|
|
286
|
+
return ComplianceRating.PERFECT
|
|
287
|
+
if score >= 90:
|
|
288
|
+
return ComplianceRating.EXCELLENT
|
|
289
|
+
if score >= 75:
|
|
290
|
+
return ComplianceRating.GOOD
|
|
291
|
+
if score >= 60:
|
|
292
|
+
return ComplianceRating.FAIR
|
|
293
|
+
return ComplianceRating.POOR
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def get_trust_rating(score: int) -> TrustRating:
|
|
297
|
+
"""Get trust rating based on score.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
score: Trust score (0-100, after confidence multiplier)
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
TrustRating enum value
|
|
304
|
+
"""
|
|
305
|
+
if score >= 80:
|
|
306
|
+
return TrustRating.HIGHLY_TRUSTED
|
|
307
|
+
if score >= 60:
|
|
308
|
+
return TrustRating.TRUSTED
|
|
309
|
+
if score >= 40:
|
|
310
|
+
return TrustRating.MODERATE_TRUST
|
|
311
|
+
if score >= 20:
|
|
312
|
+
return TrustRating.LOW_TRUST
|
|
313
|
+
return TrustRating.UNTRUSTED
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_availability_rating(score: int) -> AvailabilityRating:
|
|
317
|
+
"""Get availability rating based on score.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
score: Availability score (0-100)
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
AvailabilityRating enum value
|
|
324
|
+
"""
|
|
325
|
+
if score >= 95:
|
|
326
|
+
return AvailabilityRating.FULLY_AVAILABLE
|
|
327
|
+
if score >= 80:
|
|
328
|
+
return AvailabilityRating.AVAILABLE
|
|
329
|
+
if score >= 60:
|
|
330
|
+
return AvailabilityRating.DEGRADED
|
|
331
|
+
if score >= 40:
|
|
332
|
+
return AvailabilityRating.UNSTABLE
|
|
333
|
+
return AvailabilityRating.UNAVAILABLE
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def get_trust_confidence_multiplier(
|
|
337
|
+
has_valid_signature: bool,
|
|
338
|
+
has_invalid_signature: bool
|
|
339
|
+
) -> float:
|
|
340
|
+
"""Get trust confidence multiplier based on signature state.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
has_valid_signature: Whether a valid signature exists
|
|
344
|
+
has_invalid_signature: Whether an invalid signature exists
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Confidence multiplier: 1.0x (valid), 0.6x (none), or 0.4x (invalid)
|
|
348
|
+
"""
|
|
349
|
+
if has_invalid_signature:
|
|
350
|
+
return 0.4 # Active distrust
|
|
351
|
+
if has_valid_signature:
|
|
352
|
+
return 1.0 # Full confidence
|
|
353
|
+
return 0.6 # Unverified claims
|