capiscio-sdk 0.2.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.
@@ -0,0 +1,74 @@
1
+ """FastAPI integration for Capiscio SimpleGuard."""
2
+ from typing import Callable, Awaitable, Any, Dict
3
+ try:
4
+ from starlette.middleware.base import BaseHTTPMiddleware
5
+ from starlette.requests import Request
6
+ from starlette.responses import JSONResponse, Response
7
+ from starlette.types import ASGIApp
8
+ except ImportError:
9
+ raise ImportError("FastAPI/Starlette is required for this integration. Install with 'pip install fastapi'.")
10
+
11
+ from ..simple_guard import SimpleGuard
12
+ from ..errors import VerificationError
13
+ import time
14
+
15
+ class CapiscioMiddleware(BaseHTTPMiddleware):
16
+ """
17
+ Middleware to enforce A2A identity verification on incoming requests.
18
+ """
19
+ def __init__(self, app: ASGIApp, guard: SimpleGuard) -> None:
20
+ super().__init__(app)
21
+ self.guard = guard
22
+
23
+ async def dispatch(
24
+ self,
25
+ request: Request,
26
+ call_next: Callable[[Request], Awaitable[Response]]
27
+ ) -> Response:
28
+ # Allow health checks or public endpoints if needed
29
+ # For now, we assume everything under /agent/ needs protection
30
+ # But let's just check for the header.
31
+
32
+ if request.method == "OPTIONS":
33
+ return await call_next(request)
34
+
35
+ # RFC-002 §9.1: X-Capiscio-Badge header
36
+ auth_header = request.headers.get("X-Capiscio-Badge")
37
+
38
+ # If no header, we might let it pass but mark as unverified?
39
+ # The mandate says: "Returns 401 (missing) or 403 (invalid)."
40
+ if not auth_header:
41
+ return JSONResponse(
42
+ {"error": "Missing X-Capiscio-Badge header. This endpoint is protected by CapiscIO."},
43
+ status_code=401
44
+ )
45
+
46
+ start_time = time.perf_counter()
47
+ try:
48
+ # Read the body for integrity check
49
+ body_bytes = await request.body()
50
+
51
+ # Verify the JWS with body
52
+ payload = self.guard.verify_inbound(auth_header, body=body_bytes)
53
+
54
+ # Reset the receive channel so downstream can read the body
55
+ async def receive() -> Dict[str, Any]:
56
+ return {"type": "http.request", "body": body_bytes, "more_body": False}
57
+ request._receive = receive
58
+
59
+ # Inject claims into request.state
60
+ request.state.agent = payload
61
+ request.state.agent_id = payload.get("iss")
62
+
63
+ except VerificationError as e:
64
+ return JSONResponse({"error": f"Access Denied: {str(e)}"}, status_code=403)
65
+
66
+ verification_duration = (time.perf_counter() - start_time) * 1000
67
+
68
+ response = await call_next(request)
69
+
70
+ # Add Server-Timing header (standard for performance metrics)
71
+ # Syntax: metric_name;dur=123.4;desc="Description"
72
+ response.headers["Server-Timing"] = f"capiscio-auth;dur={verification_duration:.3f};desc=\"CapiscIO Verification\""
73
+
74
+ return response
@@ -1,5 +1,10 @@
1
1
  """Multi-dimensional scoring system for A2A validation.
2
2
 
3
+ .. deprecated:: 0.3.0
4
+ The pure Python scoring module is deprecated. Scoring is now handled by
5
+ capiscio-core via gRPC. Use :func:`capiscio_sdk.validate_agent_card` for
6
+ Agent Card validation with scoring, which delegates to Go core.
7
+
3
8
  This module provides three independent scoring dimensions:
4
9
  - Compliance: Protocol specification adherence (0-100)
5
10
  - Trust: Security and authenticity signals (0-100)
@@ -7,8 +12,14 @@ This module provides three independent scoring dimensions:
7
12
 
8
13
  Each dimension has its own rating scale and breakdown structure,
9
14
  allowing users to make nuanced decisions based on their priorities.
15
+
16
+ NOTE: The types in this module are still used for API compatibility.
17
+ The scorer classes (ComplianceScorer, TrustScorer, AvailabilityScorer)
18
+ are deprecated and should not be used directly.
10
19
  """
11
20
 
21
+ import warnings as _warnings
22
+
12
23
  from .types import (
13
24
  ComplianceScore,
14
25
  TrustScore,
@@ -21,11 +32,69 @@ from .types import (
21
32
  AvailabilityRating,
22
33
  ScoringContext,
23
34
  )
24
- from .compliance import ComplianceScorer
25
- from .trust import TrustScorer
26
- from .availability import AvailabilityScorer
35
+
36
+ # Import deprecated scorers with warnings
37
+ from .compliance import ComplianceScorer as _LegacyComplianceScorer
38
+ from .trust import TrustScorer as _LegacyTrustScorer
39
+ from .availability import AvailabilityScorer as _LegacyAvailabilityScorer
40
+
41
+
42
+ class ComplianceScorer(_LegacyComplianceScorer):
43
+ """Compliance scorer (DEPRECATED - scoring now handled by Go core).
44
+
45
+ .. deprecated:: 0.3.0
46
+ Use :func:`capiscio_sdk.validate_agent_card` which delegates
47
+ scoring to capiscio-core. This class will be removed in v1.0.0.
48
+ """
49
+
50
+ def __init__(self, *args, **kwargs):
51
+ _warnings.warn(
52
+ "ComplianceScorer is deprecated. Scoring is now handled by Go core. "
53
+ "Use capiscio_sdk.validate_agent_card() instead.",
54
+ DeprecationWarning,
55
+ stacklevel=2
56
+ )
57
+ super().__init__(*args, **kwargs)
58
+
59
+
60
+ class TrustScorer(_LegacyTrustScorer):
61
+ """Trust scorer (DEPRECATED - scoring now handled by Go core).
62
+
63
+ .. deprecated:: 0.3.0
64
+ Use :func:`capiscio_sdk.validate_agent_card` which delegates
65
+ scoring to capiscio-core. This class will be removed in v1.0.0.
66
+ """
67
+
68
+ def __init__(self, *args, **kwargs):
69
+ _warnings.warn(
70
+ "TrustScorer is deprecated. Scoring is now handled by Go core. "
71
+ "Use capiscio_sdk.validate_agent_card() instead.",
72
+ DeprecationWarning,
73
+ stacklevel=2
74
+ )
75
+ super().__init__(*args, **kwargs)
76
+
77
+
78
+ class AvailabilityScorer(_LegacyAvailabilityScorer):
79
+ """Availability scorer (DEPRECATED - scoring now handled by Go core).
80
+
81
+ .. deprecated:: 0.3.0
82
+ Use :func:`capiscio_sdk.validate_agent_card` which delegates
83
+ scoring to capiscio-core. This class will be removed in v1.0.0.
84
+ """
85
+
86
+ def __init__(self, *args, **kwargs):
87
+ _warnings.warn(
88
+ "AvailabilityScorer is deprecated. Scoring is now handled by Go core. "
89
+ "Use capiscio_sdk.validate_agent_card() instead.",
90
+ DeprecationWarning,
91
+ stacklevel=2
92
+ )
93
+ super().__init__(*args, **kwargs)
94
+
27
95
 
28
96
  __all__ = [
97
+ # Types (still used)
29
98
  "ComplianceScore",
30
99
  "TrustScore",
31
100
  "AvailabilityScore",
@@ -36,6 +105,7 @@ __all__ = [
36
105
  "TrustRating",
37
106
  "AvailabilityRating",
38
107
  "ScoringContext",
108
+ # Deprecated scorers (kept for backward compatibility)
39
109
  "ComplianceScorer",
40
110
  "TrustScorer",
41
111
  "AvailabilityScorer",
@@ -0,0 +1,346 @@
1
+ """SimpleGuard - Zero-config security for A2A agents.
2
+
3
+ This module provides signing and verification of A2A messages using
4
+ the capiscio-core Go library via gRPC. All cryptographic operations
5
+ are delegated to the Go core for consistency across SDKs.
6
+ """
7
+ import os
8
+ import json
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional, Dict, Any, Union
12
+
13
+ from .errors import ConfigurationError, VerificationError
14
+ from ._rpc.client import CapiscioRPCClient
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class SimpleGuard:
20
+ """
21
+ Zero-config security middleware for A2A agents.
22
+
23
+ SimpleGuard handles message signing and verification using Ed25519
24
+ keys. All cryptographic operations are performed by the capiscio-core
25
+ Go library via gRPC, ensuring consistent behavior across all SDKs.
26
+
27
+ Example:
28
+ >>> guard = SimpleGuard(dev_mode=True)
29
+ >>> token = guard.sign_outbound({"sub": "test"}, body=b"hello")
30
+ >>> claims = guard.verify_inbound(token, body=b"hello")
31
+
32
+ # With explicit agent_id:
33
+ >>> guard = SimpleGuard(agent_id="did:web:example.com:agents:myagent")
34
+
35
+ # In dev mode, did:key is auto-generated:
36
+ >>> guard = SimpleGuard(dev_mode=True)
37
+ >>> print(guard.agent_id) # did:key:z6Mk...
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ base_dir: Optional[Union[str, Path]] = None,
43
+ dev_mode: bool = False,
44
+ rpc_address: Optional[str] = None,
45
+ agent_id: Optional[str] = None,
46
+ badge_token: Optional[str] = None,
47
+ ) -> None:
48
+ """
49
+ Initialize SimpleGuard.
50
+
51
+ Args:
52
+ base_dir: Starting directory to search for config (defaults to cwd).
53
+ dev_mode: If True, auto-generates keys with did:key identity (RFC-002 §6.1).
54
+ rpc_address: gRPC server address. If None, auto-starts local server.
55
+ agent_id: Explicit agent DID. If None:
56
+ - In dev_mode: Auto-generates did:key from keypair
57
+ - Otherwise: Loaded from agent-card.json
58
+ badge_token: Pre-obtained badge token to use for identity. When set,
59
+ make_headers() will use this token instead of signing.
60
+ """
61
+ self.dev_mode = dev_mode
62
+ self._explicit_agent_id = agent_id
63
+ self._badge_token = badge_token
64
+
65
+ # 1. Safety Check
66
+ if self.dev_mode and os.getenv("CAPISCIO_ENV") == "prod":
67
+ logger.critical(
68
+ "CRITICAL: SimpleGuard initialized in dev_mode=True but CAPISCIO_ENV=prod. "
69
+ "This is insecure! Disable dev_mode in production."
70
+ )
71
+
72
+ # 2. Resolve base_dir
73
+ self.project_root = self._resolve_project_root(base_dir)
74
+ self.keys_dir = self.project_root / "capiscio_keys"
75
+ self.trusted_dir = self.keys_dir / "trusted"
76
+ self.agent_card_path = self.project_root / "agent-card.json"
77
+
78
+ # 3. Connect to gRPC server
79
+ self._client = CapiscioRPCClient(address=rpc_address)
80
+ self._client.connect()
81
+
82
+ # 4. Load or generate agent identity
83
+ self.agent_id: str
84
+ self.signing_kid: str
85
+ self._load_or_generate_card()
86
+
87
+ # 5. Load or generate keys via gRPC (may update agent_id with did:key)
88
+ self._load_or_generate_keys()
89
+
90
+ # 6. Load trust store
91
+ self._setup_trust_store()
92
+
93
+ def sign_outbound(self, payload: Dict[str, Any], body: Optional[bytes] = None) -> str:
94
+ """
95
+ Sign a payload for outbound transmission.
96
+
97
+ Args:
98
+ payload: The JSON payload to sign.
99
+ body: Optional HTTP body bytes to bind to the signature.
100
+
101
+ Returns:
102
+ Compact JWS string.
103
+ """
104
+ # Inject issuer if missing
105
+ if "iss" not in payload:
106
+ payload["iss"] = self.agent_id
107
+
108
+ # Use body for binding if provided
109
+ body_bytes = body or b""
110
+
111
+ # Sign via gRPC - use SignAttached which handles timestamps and body hash
112
+ jws, error = self._client.simpleguard.sign_attached(
113
+ payload=body_bytes, # This gets hashed into 'bh' claim
114
+ key_id=self.signing_kid,
115
+ headers={"iss": self.agent_id},
116
+ )
117
+
118
+ if error:
119
+ raise ConfigurationError(f"Failed to sign payload: {error}")
120
+
121
+ return jws
122
+
123
+ def verify_inbound(self, jws: str, body: Optional[bytes] = None) -> Dict[str, Any]:
124
+ """
125
+ Verify an inbound JWS.
126
+
127
+ Args:
128
+ jws: The compact JWS string.
129
+ body: Optional HTTP body bytes to verify against 'bh' claim.
130
+
131
+ Returns:
132
+ The verified payload.
133
+
134
+ Raises:
135
+ VerificationError: If signature is invalid, key is untrusted, or integrity check fails.
136
+ """
137
+ valid, payload_bytes, key_id, error = self._client.simpleguard.verify_attached(
138
+ jws=jws,
139
+ body=body,
140
+ )
141
+
142
+ if error:
143
+ logger.warning(f'{{"event": "agent_call_denied", "reason": "{error}"}}')
144
+ raise VerificationError(error)
145
+
146
+ if not valid:
147
+ raise VerificationError("Verification failed")
148
+
149
+ # Parse payload
150
+ try:
151
+ payload = json.loads(payload_bytes) if payload_bytes else {}
152
+ except json.JSONDecodeError:
153
+ payload = {}
154
+
155
+ iss = payload.get("iss", "unknown")
156
+ logger.info(f'{{"event": "agent_call_allowed", "iss": "{iss}", "kid": "{key_id}"}}')
157
+
158
+ return payload
159
+
160
+ def make_headers(self, payload: Dict[str, Any], body: Optional[bytes] = None) -> Dict[str, str]:
161
+ """
162
+ Generate headers containing the Badge (RFC-002 §9.1).
163
+
164
+ If a badge_token was provided at construction, it will be used.
165
+ Otherwise, signs the payload to create a token.
166
+
167
+ Args:
168
+ payload: The JSON payload to sign (ignored if using badge_token).
169
+ body: Optional HTTP body bytes to bind to the signature.
170
+
171
+ Returns:
172
+ Dict with X-Capiscio-Badge header.
173
+ """
174
+ if self._badge_token:
175
+ return {"X-Capiscio-Badge": self._badge_token}
176
+
177
+ token = self.sign_outbound(payload, body=body)
178
+ return {"X-Capiscio-Badge": token}
179
+
180
+ def set_badge_token(self, token: str) -> None:
181
+ """
182
+ Update the badge token used for outbound requests.
183
+
184
+ This is typically called by the badge keeper when a new token is obtained.
185
+
186
+ Args:
187
+ token: The new badge token (compact JWS).
188
+ """
189
+ self._badge_token = token
190
+ logger.debug(f"Updated badge token for agent {self.agent_id}")
191
+
192
+ def close(self) -> None:
193
+ """Close the gRPC connection."""
194
+ if self._client:
195
+ self._client.close()
196
+
197
+ def __enter__(self) -> "SimpleGuard":
198
+ return self
199
+
200
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
201
+ self.close()
202
+
203
+ def _resolve_project_root(self, base_dir: Optional[Union[str, Path]]) -> Path:
204
+ """Walk up the directory tree to find agent-card.json or stop at root."""
205
+ current = Path(base_dir or os.getcwd()).resolve()
206
+
207
+ search_path = current
208
+ while search_path != search_path.parent:
209
+ if (search_path / "agent-card.json").exists():
210
+ return search_path
211
+ search_path = search_path.parent
212
+
213
+ return current
214
+
215
+ def _load_or_generate_card(self) -> None:
216
+ """Load agent-card.json or generate a minimal one in dev_mode."""
217
+ # If explicit agent_id was provided, use it
218
+ if self._explicit_agent_id:
219
+ self.agent_id = self._explicit_agent_id
220
+ self.signing_kid = "key-0" # Will be updated when keys are generated/loaded
221
+ logger.info(f"Using explicit agent_id: {self.agent_id}")
222
+ return
223
+
224
+ if self.agent_card_path.exists():
225
+ try:
226
+ with open(self.agent_card_path, "r") as f:
227
+ data = json.load(f)
228
+ self.agent_id = data.get("agent_id")
229
+ keys = data.get("public_keys", [])
230
+ if not keys:
231
+ raise ConfigurationError("agent-card.json missing 'public_keys'.")
232
+ self.signing_kid = keys[0].get("kid")
233
+
234
+ if not self.agent_id or not self.signing_kid:
235
+ raise ConfigurationError("agent-card.json missing 'agent_id' or 'public_keys[0].kid'.")
236
+ except Exception as e:
237
+ raise ConfigurationError(f"Failed to load agent-card.json: {e}")
238
+ elif self.dev_mode:
239
+ logger.info("Dev Mode: Will generate did:key identity from keypair")
240
+ # Placeholder - will be updated with did:key after key generation
241
+ self.agent_id = "local-dev-agent" # Temporary, replaced in _load_or_generate_keys
242
+ self.signing_kid = "local-dev-key"
243
+ else:
244
+ raise ConfigurationError(f"agent-card.json not found at {self.project_root}")
245
+
246
+ def _load_or_generate_keys(self) -> None:
247
+ """Load keys or generate them in dev_mode via gRPC.
248
+
249
+ In dev_mode, if no explicit agent_id was provided, updates self.agent_id
250
+ with the did:key derived from the generated keypair (RFC-002 §6.1).
251
+ """
252
+ private_key_path = self.keys_dir / "private.pem"
253
+ public_key_path = self.keys_dir / "public.pem"
254
+
255
+ if not self.keys_dir.exists():
256
+ if self.dev_mode:
257
+ self.keys_dir.mkdir(parents=True, exist_ok=True)
258
+ self.trusted_dir.mkdir(parents=True, exist_ok=True)
259
+ else:
260
+ raise ConfigurationError(f"capiscio_keys directory not found at {self.keys_dir}")
261
+
262
+ if private_key_path.exists():
263
+ # Load existing key via gRPC
264
+ key_info, error = self._client.simpleguard.load_key(str(private_key_path))
265
+ if error:
266
+ raise ConfigurationError(f"Failed to load private.pem: {error}")
267
+ # Update signing kid to match the loaded key
268
+ self.signing_kid = key_info["key_id"]
269
+ logger.info(f"Loaded key: {self.signing_kid}")
270
+ elif self.dev_mode:
271
+ logger.info("Dev Mode: Generating Ed25519 keypair via gRPC")
272
+
273
+ # Generate via gRPC
274
+ key_info, error = self._client.simpleguard.generate_key_pair(
275
+ key_id=self.signing_kid,
276
+ )
277
+ if error:
278
+ raise ConfigurationError(f"Failed to generate keypair: {error}")
279
+
280
+ # Update agent_id with did:key if not explicitly set (RFC-002 §6.1)
281
+ did_key = key_info.get("did_key")
282
+ if did_key and not self._explicit_agent_id:
283
+ self.agent_id = did_key
284
+ logger.info(f"Dev Mode: Using did:key identity: {self.agent_id}")
285
+
286
+ # Save private key
287
+ with open(private_key_path, "w") as f:
288
+ f.write(key_info["private_key_pem"])
289
+
290
+ # Save public key
291
+ with open(public_key_path, "w") as f:
292
+ f.write(key_info["public_key_pem"])
293
+
294
+ # Update agent-card.json with JWK
295
+ self._update_agent_card_with_pem(key_info["public_key_pem"])
296
+ else:
297
+ raise ConfigurationError(f"private.pem not found at {private_key_path}")
298
+
299
+ def _update_agent_card_with_pem(self, public_key_pem: str) -> None:
300
+ """Helper to write agent-card.json with the generated key."""
301
+ # For simplicity, just create a minimal card
302
+ # In production, would convert PEM to JWK
303
+ card_data = {
304
+ "agent_id": self.agent_id,
305
+ "public_keys": [{
306
+ "kty": "OKP",
307
+ "crv": "Ed25519",
308
+ "kid": self.signing_kid,
309
+ "use": "sig",
310
+ # Note: x would need to be extracted from PEM
311
+ }],
312
+ "protocolVersion": "0.3.0",
313
+ "name": "Local Dev Agent",
314
+ "description": "Auto-generated by SimpleGuard",
315
+ "url": "http://localhost:8000",
316
+ "version": "0.1.0",
317
+ "provider": {
318
+ "organization": "Local Dev"
319
+ }
320
+ }
321
+
322
+ with open(self.agent_card_path, "w") as f:
323
+ json.dump(card_data, f, indent=2)
324
+ logger.info(f"Created agent-card.json at {self.agent_card_path}")
325
+
326
+ def _setup_trust_store(self) -> None:
327
+ """Ensure trust store exists and add self-trust in dev_mode."""
328
+ if not self.trusted_dir.exists() and self.dev_mode:
329
+ self.trusted_dir.mkdir(parents=True, exist_ok=True)
330
+
331
+ if self.dev_mode:
332
+ # Self-Trust: Load public key into gRPC server's trust store
333
+ # Use a different key_id for trust to avoid overwriting the signing key
334
+ public_key_path = self.keys_dir / "public.pem"
335
+ trust_key_id = f"{self.signing_kid}-trust"
336
+ self_trust_path = self.trusted_dir / f"{trust_key_id}.pem"
337
+
338
+ if public_key_path.exists() and not self_trust_path.exists():
339
+ import shutil
340
+ shutil.copy(public_key_path, self_trust_path)
341
+ logger.info(f"Dev Mode: Added self-trust for kid {trust_key_id}")
342
+
343
+ # Load into gRPC server trust store for verification
344
+ # (signing key is loaded separately during guard initialization)
345
+ if self_trust_path.exists():
346
+ self._client.simpleguard.load_key(str(self_trust_path))
capiscio_sdk/types.py CHANGED
@@ -1,4 +1,4 @@
1
- """Core types for Capiscio A2A Security."""
1
+ """Core types for Capiscio Python SDK."""
2
2
  from typing import List, Dict, Any, Optional
3
3
  from pydantic import BaseModel, Field
4
4
  from enum import Enum
@@ -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",