Skip to content

Commit d3dc020

Browse files
authored
feat(pip): add PolicyClient for RFC-005 PDP integration (Option B) (#10)
* feat(pip): add PolicyClient for RFC-005 PDP integration via gRPC Option B architecture: delegates all PDP decision logic to Go core's EvaluatePolicyDecision RPC. Python SDK handles obligation execution (context-dependent) and response propagation. New files: - capiscio_mcp/pip.py: PolicyClient, PIPConfig, PolicyResult, Obligation - PolicyClient.evaluate() builds request, calls gRPC, parses response - PolicyResult.execute_obligations() dispatches to registered handlers - PolicyResult.allowed/denied/pdp_error convenience properties - tests/test_pip.py: 23 tests covering: - PolicyResult properties (allowed/denied/pdp_error) - Obligation dataclass and execution - PIPConfig defaults and custom values - PolicyClient gRPC integration (mock): allow, deny, observe, cache, break-glass, obligations, bad JSON, request field fidelity Proto updates: - Regenerated gen/ stubs from capiscio-core feature/rfc005-pdp-rpc proto (adds EvaluatePolicyDecision RPC + 6 new message types) - Updated hand-written stubs with matching dataclasses for IDE support * fix: align proto with capiscio-core breakglass_public_key (bytes) - Regenerated proto stubs from capiscio-core main (PR #43 merged) - PolicyConfig.breakglass_public_key_path (string) → breakglass_public_key (bytes) - Updated PIPConfig, PolicyClient.evaluate(), and tests - Fixed regenerated grpc stub import path * fix: address PR review comments - Remove Obligation.id (MCPObligation proto has no id field) - Validate json.loads returns dict before using as params - Downgrade grpcio version check from RuntimeError to RuntimeWarning - Update tests to match proto shape (no id field on obligations)
1 parent c8f65fa commit d3dc020

6 files changed

Lines changed: 909 additions & 43 deletions

File tree

capiscio_mcp/_proto/capiscio/v1/mcp_pb2.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,75 @@ class HealthResponse:
177177
core_version: str = ""
178178
proto_version: str = ""
179179
version_compatible: bool = True
180+
181+
182+
# =============================================================================
183+
# RFC-005: Policy Decision Messages
184+
# =============================================================================
185+
186+
@dataclass
187+
class PolicySubject:
188+
"""Subject attributes for policy evaluation."""
189+
did: str = ""
190+
badge_jti: str = ""
191+
ial: str = ""
192+
trust_level: str = ""
193+
badge_exp: int = 0
194+
195+
196+
@dataclass
197+
class PolicyAction:
198+
"""Action attributes for policy evaluation."""
199+
operation: str = ""
200+
capability_class: str = ""
201+
202+
203+
@dataclass
204+
class PolicyResource:
205+
"""Resource attributes for policy evaluation."""
206+
identifier: str = ""
207+
208+
209+
@dataclass
210+
class PolicyConfig:
211+
"""PEP-level configuration for the policy decision."""
212+
pdp_endpoint: str = ""
213+
pdp_timeout_ms: int = 0
214+
enforcement_mode: str = ""
215+
pep_id: str = ""
216+
workspace: str = ""
217+
breakglass_public_key: bytes = b""
218+
219+
220+
@dataclass
221+
class PolicyDecisionRequest:
222+
"""Request message for EvaluatePolicyDecision RPC."""
223+
subject: Optional[PolicySubject] = None
224+
action: Optional[PolicyAction] = None
225+
resource: Optional[PolicyResource] = None
226+
config: Optional[PolicyConfig] = None
227+
breakglass_token: str = ""
228+
229+
230+
@dataclass
231+
class MCPObligation:
232+
"""Obligation from policy decision."""
233+
type: str = ""
234+
params_json: str = ""
235+
236+
237+
@dataclass
238+
class PolicyDecisionResponse:
239+
"""Response from centralized policy decision."""
240+
decision: str = ""
241+
decision_id: str = ""
242+
reason: str = ""
243+
ttl: int = 0
244+
obligations: List["MCPObligation"] = field(default_factory=list)
245+
enforcement_mode: str = ""
246+
cache_hit: bool = False
247+
breakglass_override: bool = False
248+
breakglass_jti: str = ""
249+
error_code: str = ""
250+
pdp_latency_ms: int = 0
251+
txn_id: str = ""

capiscio_mcp/_proto/capiscio/v1/mcp_pb2_grpc.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ async def EvaluateToolAccess(
4444
"""
4545
raise NotImplementedError("Stub - replace with generated code")
4646

47+
async def EvaluatePolicyDecision(
48+
self,
49+
request: "mcp_pb2.PolicyDecisionRequest",
50+
) -> "mcp_pb2.PolicyDecisionResponse":
51+
"""
52+
Evaluate policy decision (RFC-005).
53+
54+
Centralized PDP decision logic. Returns policy outcome
55+
including obligations — never raises gRPC errors for PDP issues.
56+
"""
57+
raise NotImplementedError("Stub - replace with generated code")
58+
4759
async def VerifyServerIdentity(
4860
self,
4961
request: "mcp_pb2.VerifyServerIdentityRequest",
@@ -87,6 +99,14 @@ async def EvaluateToolAccess(
8799
"""Evaluate tool access (RFC-006)."""
88100
raise NotImplementedError("Method not implemented!")
89101

102+
async def EvaluatePolicyDecision(
103+
self,
104+
request: "mcp_pb2.PolicyDecisionRequest",
105+
context: "grpc.aio.ServicerContext",
106+
) -> "mcp_pb2.PolicyDecisionResponse":
107+
"""Evaluate policy decision (RFC-005)."""
108+
raise NotImplementedError("Method not implemented!")
109+
90110
async def VerifyServerIdentity(
91111
self,
92112
request: "mcp_pb2.VerifyServerIdentityRequest",

0 commit comments

Comments
 (0)