Skip to content

feat(rpc): add EvaluatePolicyDecision gRPC endpoint (RFC-005 Option B)#43

Merged
beonde merged 4 commits intomainfrom
feature/rfc005-pdp-rpc
Mar 20, 2026
Merged

feat(rpc): add EvaluatePolicyDecision gRPC endpoint (RFC-005 Option B)#43
beonde merged 4 commits intomainfrom
feature/rfc005-pdp-rpc

Conversation

@beonde
Copy link
Member

@beonde beonde commented Mar 20, 2026

Summary

Centralises PDP decision logic in Go core's gRPC service so Python, Node and .NET SDKs become thin gRPC callers instead of each re-implementing the full PIP.

Architecture decision: Option B — single Go implementation of PDP evaluation exposed via gRPC RPC. SDKs call EvaluatePolicyDecision and only handle obligation execution (context-dependent: rate limiting, logging, etc.) and response propagation.

Changes

Proto (proto/capiscio/v1/mcp.proto)

  • New EvaluatePolicyDecision RPC on MCPService
  • 6 new messages: PolicyDecisionRequest, PolicyDecisionResponse, PolicySubject, PolicyAction, PolicyResource, PolicyConfig
  • Response includes: decision, decision_id, reason, ttl, obligations, enforcement_mode, cache_hit, breakglass_override, breakglass_jti, error_code, pdp_latency_ms, txn_id

Handler (internal/rpc/policy_decision.go, ~310 lines)

  • Full PDP query with configurable timeout
  • Enforcement mode semantics: EM-GUARD, EM-OBSERVE, EM-AUDIT, EM-NONE
  • Break-glass JWS override with Ed25519 validation
  • In-memory decision cache with TTL bounded by badge expiry
  • Obligation marshalling via ObligationRegistry
  • PDP unavailability returns response (not RPC error) per protocol:
    • EM-OBSERVE → ALLOW_OBSERVE + error_code
    • Others → DENY + error_code
  • Transaction ID (UUID v7) on every response

Service (internal/rpc/mcp_service.go)

  • Added decisionCache and obligationReg fields to MCPService
  • Initialised in constructor

Tests (internal/rpc/policy_decision_test.go, 23 cases)

  • NoPDP, PDPAllow, PDPDeny (Guard/Observe)
  • PDPUnavailable (fail-closed + observe mode)
  • Break-glass override, cache hit, cached deny observe override
  • Timeout, invalid responses, obligation marshalling
  • All enforcement modes, TxnID format, PDP request fidelity

Protocol Boundary

Go core (this PR) SDK responsibility
PDP query + timeout Obligation execution
Decision cache Response propagation
Break-glass validation Error handling
Enforcement mode logic Transport (HTTP/gRPC)
Telemetry emission Context-specific actions

Test Results

  • go vet ./... — clean
  • go test ./... — 17/17 packages pass (excl. integration)
  • 23 new test cases all passing

Related

Centralise PDP decision logic in Go core so Python, Node and .NET SDKs
become thin gRPC callers instead of each re-implementing the full PIP.

Proto changes:
- Add EvaluatePolicyDecision RPC to MCPService
- Add PolicyDecisionRequest/Response and sub-messages
  (PolicySubject, PolicyAction, PolicyResource, PolicyConfig)

Handler (internal/rpc/policy_decision.go):
- Full PDP query with configurable timeout
- Enforcement mode semantics (EM-GUARD/OBSERVE/AUDIT/NONE)
- Break-glass JWS override with Ed25519 validation
- In-memory decision cache with TTL bounded by badge expiry
- Obligation marshalling via ObligationRegistry
- PDP unavailability returns response (not error) per protocol:
  EM-OBSERVE → ALLOW_OBSERVE + error_code
  others     → DENY + error_code
- Transaction ID (UUID v7) on every response

Tests (23 cases):
- NoPDP, PDPAllow/Deny, PDPUnavailable (fail-closed + observe)
- Break-glass, cache hit, cached deny observe override
- Timeout, invalid responses, obligation marshalling
- All enforcement modes, TxnID format, PDP request fidelity
Copilot AI review requested due to automatic review settings March 20, 2026 18:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an RFC-005 “Option B” gRPC surface to centralize PDP-backed policy evaluation in capiscio-core so SDKs can call a single RPC for policy decisions.

Changes:

  • Extends MCPService with a new EvaluatePolicyDecision RPC and associated request/response messages.
  • Implements EvaluatePolicyDecision server-side logic (PDP call, cache lookup, break-glass handling, response shaping).
  • Adds unit tests and regenerates Go protobuf/grpc bindings.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
proto/capiscio/v1/mcp.proto Adds the new RPC and RFC-005 policy decision message types/fields.
pkg/rpc/gen/capiscio/v1/mcp_grpc.pb.go Regenerated gRPC client/server stubs to include EvaluatePolicyDecision.
pkg/rpc/gen/capiscio/v1/mcp.pb.go Regenerated protobuf message types for the new RFC-005 messages.
internal/rpc/policy_decision.go Implements the new RPC handler (PDP query, caching, break-glass, obligations handling).
internal/rpc/policy_decision_test.go Adds test coverage for the new RPC behavior.
internal/rpc/mcp_service.go Wires new cache/obligation registry fields into MCPService construction.

@codecov
Copy link

codecov bot commented Mar 20, 2026

Codecov Report

❌ Patch coverage is 51.89873% with 190 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/rpc/gen/capiscio/v1/mcp.pb.go 0.00% 149 Missing ⚠️
internal/rpc/policy_decision.go 89.73% 14 Missing and 9 partials ⚠️
pkg/rpc/gen/capiscio/v1/mcp_grpc.pb.go 0.00% 18 Missing ⚠️

📢 Thoughts on this report? Let us know!

beonde added 2 commits March 20, 2026 14:44
- Proto: change breakglass_public_key_path (string) to breakglass_public_key (bytes)
  to eliminate filesystem access from RPC boundary (comments #8, #9)
- Remove server-side obligation enforcement; obligations returned to SDK
  caller per Option B protocol boundary (comments #1, #2, #3)
- Sanitize error messages in handlePDPUnavailable; log raw errors server-side,
  return stable 'policy service unavailable' to callers (comment #6)
- Add classifyPDPError to distinguish pdp_timeout, pdp_invalid_response,
  and pdp_unavailable error codes (comment #7)
- Validate enforcement_mode before badge-only early return (comment #10)
- Fix newTestService to propagate errors via require.NoError (comment #5)
- Add break-glass test coverage: valid override, wrong key, no key configured,
  scope mismatch, bad key size (comment #4)
- Add InvalidEnforcementMode_NoPDP test for badge-only + invalid mode
- Add TestClassifyPDPError with 8 error classification cases
Extract buildPIPRequest helper to move 3 conditional branches out of the
main handler, reducing gocyclo from 16 to 13 (limit: 15).
Copilot AI review requested due to automatic review settings March 20, 2026 19:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

- badgeExpTTL returns 0 when badge_exp missing (skip cache per RFC-005) (#11)
- Remove unused pipReq param from handleBreakGlass (#12)
- Include enforcement_mode in cache key via variadic CacheKeyComponents (#17)
- Handle context.Canceled as client cancellation (return ctx.Err()),
  not as PDP timeout (#19)
- Extract queryPDP helper to reduce EvaluatePolicyDecision complexity to 11
@beonde beonde merged commit 3a5d5e3 into main Mar 20, 2026
3 checks passed
@beonde beonde deleted the feature/rfc005-pdp-rpc branch March 20, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants