capiscio-sdk 0.2.0__tar.gz → 2.3.0__tar.gz

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.
Files changed (116) hide show
  1. capiscio_sdk-2.3.0/.github/copilot-instructions.md +511 -0
  2. capiscio_sdk-2.3.0/.github/workflows/integration-tests.yml +238 -0
  3. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/.github/workflows/pr-checks.yml +7 -3
  4. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/.github/workflows/publish.yml +4 -2
  5. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/.gitignore +6 -0
  6. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/CHANGELOG.md +51 -7
  7. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/CONTRIBUTING.md +1 -1
  8. capiscio_sdk-2.3.0/Dockerfile.test +23 -0
  9. capiscio_sdk-2.3.0/PKG-INFO +532 -0
  10. capiscio_sdk-2.3.0/README.md +486 -0
  11. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/RELEASE_GUIDE.md +1 -1
  12. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/SECURITY.md +3 -2
  13. capiscio_sdk-2.3.0/capiscio_sdk/__init__.py +110 -0
  14. capiscio_sdk-2.3.0/capiscio_sdk/_rpc/__init__.py +7 -0
  15. capiscio_sdk-2.3.0/capiscio_sdk/_rpc/client.py +1321 -0
  16. capiscio_sdk-2.3.0/capiscio_sdk/_rpc/gen/__init__.py +1 -0
  17. capiscio_sdk-2.3.0/capiscio_sdk/_rpc/process.py +232 -0
  18. capiscio_sdk-2.3.0/capiscio_sdk/badge.py +737 -0
  19. capiscio_sdk-2.3.0/capiscio_sdk/badge_keeper.py +304 -0
  20. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/config.py +1 -1
  21. capiscio_sdk-2.3.0/capiscio_sdk/dv.py +296 -0
  22. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/errors.py +11 -1
  23. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/executor.py +17 -0
  24. capiscio_sdk-2.3.0/capiscio_sdk/integrations/fastapi.py +74 -0
  25. capiscio_sdk-2.3.0/capiscio_sdk/scoring/__init__.py +112 -0
  26. capiscio_sdk-2.3.0/capiscio_sdk/simple_guard.py +346 -0
  27. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/types.py +1 -1
  28. capiscio_sdk-2.3.0/capiscio_sdk/validators/__init__.py +75 -0
  29. capiscio_sdk-2.3.0/capiscio_sdk/validators/_core.py +376 -0
  30. capiscio_sdk-2.3.0/docs/api-reference.md +79 -0
  31. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/getting-started/concepts.md +5 -5
  32. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/getting-started/installation.md +2 -2
  33. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/getting-started/quickstart.md +2 -2
  34. capiscio_sdk-2.3.0/docs/guides/badge-verification.md +238 -0
  35. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/guides/configuration.md +4 -4
  36. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/guides/scoring.md +4 -4
  37. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/index.md +7 -7
  38. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/javascripts/extra.js +1 -1
  39. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/stylesheets/extra.css +1 -1
  40. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/examples/README.md +3 -3
  41. capiscio_sdk-2.3.0/examples/secure_ping_pong/README.md +47 -0
  42. capiscio_sdk-2.3.0/examples/secure_ping_pong/client.py +122 -0
  43. capiscio_sdk-2.3.0/examples/secure_ping_pong/server.py +30 -0
  44. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/examples/simple_agent/README.md +3 -3
  45. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/examples/simple_agent/agent_executor.py +2 -2
  46. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/examples/simple_agent/requirements.txt +1 -1
  47. capiscio_sdk-2.3.0/mkdocs.yml +65 -0
  48. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/pyproject.toml +46 -1
  49. capiscio_sdk-2.3.0/tests/integration/Dockerfile.test +33 -0
  50. capiscio_sdk-2.3.0/tests/integration/README.md +130 -0
  51. capiscio_sdk-2.3.0/tests/integration/docker-compose.yml +62 -0
  52. capiscio_sdk-2.3.0/tests/integration/requirements.txt +7 -0
  53. capiscio_sdk-2.3.0/tests/integration/test_badge_keeper.py +322 -0
  54. capiscio_sdk-2.3.0/tests/integration/test_dv_badge_flow.py +671 -0
  55. capiscio_sdk-2.3.0/tests/integration/test_dv_order_api.py +295 -0
  56. capiscio_sdk-2.3.0/tests/integration/test_dv_sdk.py +156 -0
  57. capiscio_sdk-2.3.0/tests/integration/test_grpc_scoring.py +102 -0
  58. capiscio_sdk-2.3.0/tests/integration/test_server_integration.py +413 -0
  59. capiscio_sdk-2.3.0/tests/integration/test_simple_guard.py +268 -0
  60. capiscio_sdk-2.3.0/tests/unit/test_badge.py +494 -0
  61. capiscio_sdk-2.3.0/tests/unit/test_badge_keeper.py +342 -0
  62. capiscio_sdk-2.3.0/tests/unit/test_core_validator.py +249 -0
  63. capiscio_sdk-2.3.0/tests/unit/test_fastapi_integration.py +106 -0
  64. capiscio_sdk-2.3.0/tests/unit/test_pop_badge.py +302 -0
  65. capiscio_sdk-2.3.0/tests/unit/test_simple_guard.py +204 -0
  66. capiscio_sdk-0.2.0/PKG-INFO +0 -221
  67. capiscio_sdk-0.2.0/README.md +0 -182
  68. capiscio_sdk-0.2.0/capiscio_sdk/__init__.py +0 -42
  69. capiscio_sdk-0.2.0/capiscio_sdk/scoring/__init__.py +0 -42
  70. capiscio_sdk-0.2.0/capiscio_sdk/validators/__init__.py +0 -18
  71. capiscio_sdk-0.2.0/mkdocs.yml +0 -289
  72. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/.github/markdown-link-check-config.json +0 -0
  73. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/.github/workflows/docs.yml +0 -0
  74. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/.python-version +0 -0
  75. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/LICENSE +0 -0
  76. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/QUICK_REFERENCE.md +0 -0
  77. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/infrastructure/__init__.py +0 -0
  78. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/infrastructure/cache.py +0 -0
  79. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/infrastructure/rate_limiter.py +0 -0
  80. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/py.typed +0 -0
  81. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/scoring/availability.py +0 -0
  82. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/scoring/compliance.py +0 -0
  83. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/scoring/trust.py +0 -0
  84. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/scoring/types.py +0 -0
  85. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/agent_card.py +0 -0
  86. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/certificate.py +0 -0
  87. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/message.py +0 -0
  88. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/protocol.py +0 -0
  89. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/semver.py +0 -0
  90. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/signature.py +0 -0
  91. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/capiscio_sdk/validators/url_security.py +0 -0
  92. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/assets/.!58931!favicon.ico +0 -0
  93. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/assets/favicon.ico +0 -0
  94. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/assets/logo.png +0 -0
  95. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/includes/abbreviations.md +0 -0
  96. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/docs/stylesheets/unified.css +0 -0
  97. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/examples/simple_agent/main.py +0 -0
  98. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/examples/simple_agent/test_client.py +0 -0
  99. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/__init__.py +0 -0
  100. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/e2e/__init__.py +0 -0
  101. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/integration/__init__.py +0 -0
  102. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/integration/test_real_executor.py +0 -0
  103. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/__init__.py +0 -0
  104. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_agent_card.py +0 -0
  105. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_cache.py +0 -0
  106. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_certificate.py +0 -0
  107. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_config.py +0 -0
  108. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_errors.py +0 -0
  109. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_executor.py +0 -0
  110. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_message_validator.py +0 -0
  111. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_protocol_validator.py +0 -0
  112. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_rate_limiter.py +0 -0
  113. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_semver_validator.py +0 -0
  114. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_signature_validator.py +0 -0
  115. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_types.py +0 -0
  116. {capiscio_sdk-0.2.0 → capiscio_sdk-2.3.0}/tests/unit/test_url_security.py +0 -0
@@ -0,0 +1,511 @@
1
+ # capiscio-sdk-python - GitHub Copilot Instructions
2
+
3
+ ## 🛑 ABSOLUTE RULES - NO EXCEPTIONS
4
+
5
+ These rules are non-negotiable. Violating them will cause production issues.
6
+
7
+ ### 1. ALL WORK VIA PULL REQUESTS
8
+ - **NEVER commit directly to `main`.** All changes MUST go through PRs.
9
+ - Create feature branches: `feature/`, `fix/`, `chore/`
10
+ - PRs require CI to pass before merge consideration
11
+
12
+ ### 2. LOCAL CI VALIDATION BEFORE PUSH
13
+ - **ALL tests MUST pass locally before pushing to a PR.**
14
+ - Run: `pytest -v`
15
+ - If tests fail locally, fix them before pushing. Never push failing code.
16
+
17
+ ### 3. RFCs ARE READ-ONLY
18
+ - **DO NOT modify RFCs without explicit team authorization.**
19
+ - Implementation must conform to RFCs in `capiscio-rfcs/`
20
+
21
+ ### 4. NO WATCH/BLOCKING COMMANDS
22
+ - **NEVER run blocking commands** without timeout
23
+ - Use `timeout` wrapper for long-running commands
24
+
25
+ ---
26
+
27
+ ## Repository Purpose
28
+
29
+ **capiscio-sdk-python** is the official Python SDK for CapiscIO, providing:
30
+ - SimpleGuard: Runtime badge verification middleware
31
+ - gRPC Client: Interface to capiscio-core gRPC services
32
+ - Badge verification utilities
33
+ - DID resolution helpers
34
+
35
+ **Technology Stack**: Python 3.9+, gRPC, cryptography, FastAPI/Flask integration
36
+
37
+ ## Architecture
38
+
39
+ ```
40
+ capiscio_sdk/
41
+ ├── __init__.py
42
+ ├── simple_guard.py # Middleware for FastAPI/Flask
43
+ ├── grpc_client.py # gRPC client for capiscio-core
44
+ ├── badge_verifier.py # Badge verification logic
45
+ ├── did_resolver.py # DID resolution
46
+ ├── models.py # Data models
47
+ └── exceptions.py # Custom exceptions
48
+ ```
49
+
50
+ ## Critical Development Rules
51
+
52
+ ### 1. SimpleGuard Middleware
53
+
54
+ **FastAPI Integration:**
55
+ ```python
56
+ from fastapi import FastAPI, HTTPException, Request
57
+ from capiscio_sdk import SimpleGuard, BadgeVerificationError
58
+
59
+ app = FastAPI()
60
+
61
+ # Initialize SimpleGuard
62
+ guard = SimpleGuard(
63
+ issuer_url="https://registry.capisc.io",
64
+ min_trust_level=1, # Don't accept self-signed
65
+ cache_ttl=300, # 5 minutes
66
+ )
67
+
68
+ @app.middleware("http")
69
+ async def verify_badge_middleware(request: Request, call_next):
70
+ # Skip health checks
71
+ if request.url.path == "/health":
72
+ return await call_next(request)
73
+
74
+ # Extract badge from header
75
+ badge_token = request.headers.get("X-CapiscIO-Badge")
76
+ if not badge_token:
77
+ raise HTTPException(status_code=401, detail="Missing badge")
78
+
79
+ # Verify badge
80
+ try:
81
+ badge = await guard.verify(badge_token)
82
+ request.state.badge = badge
83
+ request.state.agent_did = badge.subject
84
+ except BadgeVerificationError as e:
85
+ raise HTTPException(status_code=401, detail=str(e))
86
+
87
+ return await call_next(request)
88
+
89
+ @app.get("/protected")
90
+ async def protected_route(request: Request):
91
+ agent_did = request.state.agent_did
92
+ return {"message": f"Hello {agent_did}"}
93
+ ```
94
+
95
+ **Flask Integration:**
96
+ ```python
97
+ from flask import Flask, request, g, jsonify
98
+ from capiscio_sdk import SimpleGuard, BadgeVerificationError
99
+
100
+ app = Flask(__name__)
101
+
102
+ guard = SimpleGuard(
103
+ issuer_url="https://registry.capisc.io",
104
+ min_trust_level=1,
105
+ )
106
+
107
+ @app.before_request
108
+ def verify_badge():
109
+ # Skip health checks
110
+ if request.path == "/health":
111
+ return None
112
+
113
+ # Extract badge
114
+ badge_token = request.headers.get("X-CapiscIO-Badge")
115
+ if not badge_token:
116
+ return jsonify({"error": "Missing badge"}), 401
117
+
118
+ # Verify badge
119
+ try:
120
+ badge = guard.verify_sync(badge_token)
121
+ g.badge = badge
122
+ g.agent_did = badge.subject
123
+ except BadgeVerificationError as e:
124
+ return jsonify({"error": str(e)}), 401
125
+
126
+ @app.route("/protected")
127
+ def protected_route():
128
+ return jsonify({"message": f"Hello {g.agent_did}"})
129
+ ```
130
+
131
+ ### 2. gRPC Client Usage
132
+
133
+ **Initialize Client:**
134
+ ```python
135
+ from capiscio_sdk import CapiscioGRPCClient
136
+
137
+ # Connect to capiscio-core gRPC server
138
+ client = CapiscioGRPCClient(
139
+ address="localhost:50051",
140
+ secure=False, # Use TLS in production
141
+ )
142
+ ```
143
+
144
+ **Badge Verification:**
145
+ ```python
146
+ from capiscio_sdk.grpc_client import VerifyBadgeRequest
147
+
148
+ # Verify badge via gRPC
149
+ request = VerifyBadgeRequest(
150
+ token="eyJhbGc...",
151
+ issuer_url="https://registry.capisc.io",
152
+ )
153
+
154
+ response = client.verify_badge(request)
155
+
156
+ if response.valid:
157
+ print(f"Badge valid for {response.badge.subject}")
158
+ print(f"Trust level: {response.badge.trust_level}")
159
+ else:
160
+ print(f"Badge invalid: {response.error}")
161
+ ```
162
+
163
+ **DID Resolution:**
164
+ ```python
165
+ from capiscio_sdk.grpc_client import ResolveDIDRequest
166
+
167
+ request = ResolveDIDRequest(
168
+ did="did:web:registry.capisc.io:agents:my-agent"
169
+ )
170
+
171
+ response = client.resolve_did(request)
172
+
173
+ if response.success:
174
+ print(f"Public key: {response.did_document.verification_method[0].public_key_jwk}")
175
+ else:
176
+ print(f"Resolution failed: {response.error}")
177
+ ```
178
+
179
+ **Gateway Validation:**
180
+ ```python
181
+ from capiscio_sdk.grpc_client import ValidateGatewayRequest
182
+
183
+ # Validate incoming request at gateway
184
+ request = ValidateGatewayRequest(
185
+ badge_token="eyJhbGc...",
186
+ target_url="https://agent.example.com/endpoint",
187
+ min_trust_level=1,
188
+ )
189
+
190
+ response = client.validate_gateway(request)
191
+
192
+ if response.allowed:
193
+ print(f"Request allowed for agent: {response.agent_did}")
194
+ else:
195
+ print(f"Request denied: {response.reason}")
196
+ ```
197
+
198
+ ### 3. Badge Verification Logic
199
+
200
+ **Core Verification Function:**
201
+ ```python
202
+ import jwt
203
+ import requests
204
+ from typing import Dict, Any
205
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
206
+
207
+ class BadgeVerifier:
208
+ def __init__(self, issuer_url: str, min_trust_level: int = 1):
209
+ self.issuer_url = issuer_url
210
+ self.min_trust_level = min_trust_level
211
+ self._jwks_cache: Dict[str, Any] = {}
212
+
213
+ async def verify(self, token: str) -> Badge:
214
+ # Step 1: Parse token
215
+ unverified_claims = jwt.decode(
216
+ token,
217
+ options={"verify_signature": False}
218
+ )
219
+
220
+ # Step 2: Fetch JWKS
221
+ jwks = await self._fetch_jwks(unverified_claims["iss"])
222
+
223
+ # Step 3: Verify signature
224
+ try:
225
+ claims = jwt.decode(
226
+ token,
227
+ key=jwks,
228
+ algorithms=["EdDSA"],
229
+ options={"verify_signature": True}
230
+ )
231
+ except jwt.InvalidSignatureError:
232
+ raise BadgeVerificationError("Invalid signature")
233
+
234
+ # Step 4: Validate claims
235
+ self._validate_claims(claims)
236
+
237
+ # Step 5: Create badge object
238
+ return Badge.from_claims(claims)
239
+
240
+ async def _fetch_jwks(self, issuer: str) -> Dict[str, Any]:
241
+ # Check cache
242
+ if issuer in self._jwks_cache:
243
+ return self._jwks_cache[issuer]
244
+
245
+ # Fetch from issuer
246
+ jwks_url = f"{issuer}/.well-known/jwks.json"
247
+ response = requests.get(jwks_url, timeout=5)
248
+ response.raise_for_status()
249
+
250
+ jwks = response.json()
251
+ self._jwks_cache[issuer] = jwks
252
+
253
+ return jwks
254
+
255
+ def _validate_claims(self, claims: Dict[str, Any]) -> None:
256
+ # Check required claims
257
+ required = ["iss", "sub", "jti", "exp", "iat", "trust_level"]
258
+ for claim in required:
259
+ if claim not in claims:
260
+ raise BadgeVerificationError(f"Missing claim: {claim}")
261
+
262
+ # Check expiration
263
+ if claims["exp"] < time.time():
264
+ raise BadgeVerificationError("Badge expired")
265
+
266
+ # Check trust level
267
+ if claims["trust_level"] < self.min_trust_level:
268
+ raise BadgeVerificationError(
269
+ f"Trust level {claims['trust_level']} below minimum {self.min_trust_level}"
270
+ )
271
+
272
+ # Check DID format
273
+ if not claims["sub"].startswith("did:"):
274
+ raise BadgeVerificationError("Invalid DID format")
275
+ ```
276
+
277
+ ### 4. Data Models
278
+
279
+ **Badge Model:**
280
+ ```python
281
+ from dataclasses import dataclass
282
+ from typing import Optional
283
+ from datetime import datetime
284
+
285
+ @dataclass
286
+ class Badge:
287
+ issuer: str
288
+ subject: str # Agent DID
289
+ token_id: str
290
+ expires_at: datetime
291
+ issued_at: datetime
292
+ not_before: Optional[datetime]
293
+ trust_level: int
294
+ ial: Optional[int] = None
295
+ cnf: Optional[dict] = None
296
+
297
+ @classmethod
298
+ def from_claims(cls, claims: dict) -> "Badge":
299
+ return cls(
300
+ issuer=claims["iss"],
301
+ subject=claims["sub"],
302
+ token_id=claims["jti"],
303
+ expires_at=datetime.fromtimestamp(claims["exp"]),
304
+ issued_at=datetime.fromtimestamp(claims["iat"]),
305
+ not_before=datetime.fromtimestamp(claims["nbf"]) if "nbf" in claims else None,
306
+ trust_level=claims["trust_level"],
307
+ ial=claims.get("ial"),
308
+ cnf=claims.get("cnf"),
309
+ )
310
+
311
+ @property
312
+ def is_expired(self) -> bool:
313
+ return datetime.now() > self.expires_at
314
+
315
+ @property
316
+ def is_ial1(self) -> bool:
317
+ return self.ial == 1 and self.cnf is not None
318
+ ```
319
+
320
+ ### 5. Exception Handling
321
+
322
+ **Custom Exceptions:**
323
+ ```python
324
+ class BadgeVerificationError(Exception):
325
+ """Raised when badge verification fails"""
326
+ pass
327
+
328
+ class DIDResolutionError(Exception):
329
+ """Raised when DID resolution fails"""
330
+ pass
331
+
332
+ class GRPCConnectionError(Exception):
333
+ """Raised when gRPC connection fails"""
334
+ pass
335
+ ```
336
+
337
+ **Usage:**
338
+ ```python
339
+ try:
340
+ badge = await guard.verify(token)
341
+ except BadgeVerificationError as e:
342
+ logger.error(f"Badge verification failed: {e}")
343
+ raise HTTPException(status_code=401, detail=str(e))
344
+ except Exception as e:
345
+ logger.exception("Unexpected error during verification")
346
+ raise HTTPException(status_code=500, detail="Internal server error")
347
+ ```
348
+
349
+ ## Testing
350
+
351
+ ### Unit Tests
352
+ ```python
353
+ import pytest
354
+ from capiscio_sdk import BadgeVerifier, BadgeVerificationError
355
+
356
+ @pytest.mark.asyncio
357
+ async def test_verify_valid_badge():
358
+ verifier = BadgeVerifier(
359
+ issuer_url="https://registry.capisc.io",
360
+ min_trust_level=1
361
+ )
362
+
363
+ token = generate_test_badge() # Helper function
364
+
365
+ badge = await verifier.verify(token)
366
+
367
+ assert badge.trust_level >= 1
368
+ assert badge.subject.startswith("did:")
369
+
370
+ @pytest.mark.asyncio
371
+ async def test_verify_expired_badge():
372
+ verifier = BadgeVerifier(
373
+ issuer_url="https://registry.capisc.io",
374
+ min_trust_level=1
375
+ )
376
+
377
+ token = generate_expired_badge()
378
+
379
+ with pytest.raises(BadgeVerificationError, match="expired"):
380
+ await verifier.verify(token)
381
+ ```
382
+
383
+ ### Integration Tests
384
+ ```python
385
+ @pytest.mark.integration
386
+ def test_grpc_client_verify_badge():
387
+ client = CapiscioGRPCClient(address="localhost:50051")
388
+
389
+ request = VerifyBadgeRequest(token=test_token)
390
+ response = client.verify_badge(request)
391
+
392
+ assert response.valid
393
+ assert response.badge.trust_level >= 1
394
+ ```
395
+
396
+ ## Common Development Tasks
397
+
398
+ ### Installing
399
+ ```bash
400
+ # Install in development mode
401
+ pip install -e .
402
+
403
+ # Install with all extras
404
+ pip install -e ".[dev,grpc]"
405
+ ```
406
+
407
+ ### Running Tests
408
+ ```bash
409
+ # Run all tests
410
+ pytest
411
+
412
+ # Run with coverage
413
+ pytest --cov=capiscio_sdk --cov-report=html
414
+
415
+ # Run specific test
416
+ pytest tests/test_simple_guard.py -v
417
+ ```
418
+
419
+ ### Type Checking
420
+ ```bash
421
+ # Run mypy
422
+ mypy capiscio_sdk/
423
+
424
+ # Run with strict mode
425
+ mypy --strict capiscio_sdk/
426
+ ```
427
+
428
+ ### Linting
429
+ ```bash
430
+ # Run ruff
431
+ ruff check capiscio_sdk/
432
+
433
+ # Auto-fix
434
+ ruff check --fix capiscio_sdk/
435
+
436
+ # Format
437
+ ruff format capiscio_sdk/
438
+ ```
439
+
440
+ ## Code Quality Standards
441
+
442
+ ### 1. Type Hints
443
+ ```python
444
+ # ✅ Use type hints
445
+ async def verify(self, token: str) -> Badge:
446
+ pass
447
+
448
+ # ❌ No type hints
449
+ async def verify(self, token):
450
+ pass
451
+ ```
452
+
453
+ ### 2. Docstrings
454
+ ```python
455
+ def verify_sync(self, token: str) -> Badge:
456
+ """Verify a badge synchronously.
457
+
458
+ Args:
459
+ token: The JWS badge token
460
+
461
+ Returns:
462
+ Badge: The verified badge object
463
+
464
+ Raises:
465
+ BadgeVerificationError: If verification fails
466
+ """
467
+ pass
468
+ ```
469
+
470
+ ### 3. Async/Await
471
+ ```python
472
+ # ✅ Use async for I/O operations
473
+ async def fetch_jwks(self, issuer: str) -> dict:
474
+ async with aiohttp.ClientSession() as session:
475
+ async with session.get(f"{issuer}/.well-known/jwks.json") as response:
476
+ return await response.json()
477
+
478
+ # Also provide sync version for compatibility
479
+ def fetch_jwks_sync(self, issuer: str) -> dict:
480
+ response = requests.get(f"{issuer}/.well-known/jwks.json")
481
+ return response.json()
482
+ ```
483
+
484
+ ## Common Pitfalls
485
+
486
+ 1. **Don't skip signature verification** - Always verify JWS
487
+ 2. **Don't accept self-signed badges in production** - Set min_trust_level >= 1
488
+ 3. **Don't cache badges forever** - Implement TTL
489
+ 4. **Don't ignore exceptions** - Handle verification errors properly
490
+ 5. **Don't log sensitive data** - Redact tokens in logs
491
+
492
+ ## Environment Variables
493
+
494
+ ```bash
495
+ # gRPC Client
496
+ CAPISCIO_GRPC_ADDRESS="localhost:50051"
497
+ CAPISCIO_GRPC_SECURE="false"
498
+
499
+ # Badge Verification
500
+ CAPISCIO_ISSUER_URL="https://registry.capisc.io"
501
+ CAPISCIO_MIN_TRUST_LEVEL="1"
502
+ CAPISCIO_CACHE_TTL="300"
503
+ ```
504
+
505
+ ## References
506
+
507
+ - gRPC documentation: 225 lines in docs/grpc-integration.md
508
+ - Badge verification: docs/badge-verification.md
509
+ - DID resolution: docs/did-resolution.md
510
+ - RFC-002: https://github.com/capiscio/capiscio-rfcs/blob/main/docs/002-trust-badge.md
511
+ - RFC-003: https://github.com/capiscio/capiscio-rfcs/blob/main/docs/003-key-ownership-proof.md