feat(gateway): wire RFC-011 AsyncEmitter into PEP middleware#90
Conversation
- Add RuntimeEmitter field to PEPConfig for RFC-011 event emission - Add emitter field to pep struct and wire in NewPolicyMiddleware - Emit identity.verified event after successful badge verification - Emit identity.invalid event on badge verification failure - Emit authority.granted/denied events on chain verification - Emit execution.started and execution.completed events for request lifecycle - Add helper functions for emitting events with proper mediation context - Add parseTrustLevel/parseIAL helpers to convert string levels to int Tests: - Add event_emission_test.go with tests for: - identity.verified emission on valid badge - identity.invalid emission on missing badge - execution lifecycle (started/completed) emission - graceful handling when emitter is nil Implements P1 of the Verification Locality plan. RFC: RFC-011 §5.1, §5.2, §5.4
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR integrates the RFC-011 mediation.AsyncEmitter into the gateway PEP middleware so the gateway can emit runtime events during identity verification, authority-chain handling, and request execution.
Changes:
- Adds
RuntimeEmitter *mediation.AsyncEmittertogateway.PEPConfigand wires it into the PEP middleware instance. - Emits RFC-011 identity and execution lifecycle events from key points in the request flow.
- Adds a new gateway test suite validating event emission behavior (including nil-emitter “silent mode”).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| pkg/gateway/middleware.go | Wires an optional RFC-011 async emitter into the PEP middleware and adds helper functions to emit identity/authority/execution events. |
| pkg/gateway/event_emission_test.go | Adds tests that validate runtime event emission for identity verification and execution lifecycle, plus nil-emitter behavior. |
- Clarify RuntimeEmitter doc comment re: blocking behavior (DropOnFull=true) - Remove duplicate txnID generation in buildPIPRequest (set by serveHTTP) - Pass LeafCapability in emitAuthorityDenied for leaf subject mismatch - Add subjectDID parameter to emitExecutionCompleted for correlation - Add authority.denied emission in handleChainError for chain failures - Replace time.Sleep with require.Eventually in tests for determinism Addresses: copilot-pull-request-reviewer comments on PR #90
- Remove duplicate authority.denied emission (emit only in handleChainError) - Pass LeafCapability to handleChainError for meaningful denial events - Simplify emitAuthorityDenied signature (remove unused errorCode/reason) - Add trust level 4 to parseTrustLevel (RFC-002 defines 0-4) - Add warning log when UUIDv7 generation fails and falls back to v4 Addresses: copilot-pull-request-reviewer comments on PR #90
- Add statusCapturingResponseWriter to capture HTTP status codes - Add executionState struct for tracking execution lifecycle - Implement defer-based pattern ensuring execution events on ALL exit paths - Add EmitExecutionAborted to AsyncEmitter for early terminations - Add emitExecutionAborted helper and outcomeFromStatus for middleware - Create WithState variants of key handlers for state tracking - Add tests for execution.aborted and outcome-based execution.completed - outcome field now reflects actual HTTP status (success/client_error/server_error) RFC-011 §7.1 PEP Emission Requirements: - MUST emit execution.completed OR execution.aborted on every exit path - execution.completed: outcome derived from HTTP status code - execution.aborted: emitted for auth failures, chain errors, PDP deny Addresses PR review comments: - Incomplete execution lifecycle (PRRT_kwDOQYQ-986FMftQ) - execution.completed always 'success' (PRRT_kwDOQYQ-986FMfti) - Missing test coverage (PRRT_kwDOQYQ-986FMfuU)
RFC-011 §7.1 Execution Lifecycle Implementation CompleteAddressed all remaining review feedback in commit Changes Made1. Execution Lifecycle Defer Pattern
2. Outcome Derived from HTTP Status
3. Execution Aborted Events
4. WithState Handler Variants
5. New Test Coverage
All gateway tests passing (including new tests). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
pkg/gateway/middleware.go:516
- Event emission for
authority.granted/authority.denied(andexecution.abortedpaths) is introduced/expanded here, but the new test suite only covers identity events and execution.completed for successful requests. Please add tests that assert: (1)authority.grantedis emitted on successful chain verification, (2)authority.deniedis emitted on chain verification failures (including EM-OBSERVE pass-through), and (3)execution.abortedis emitted on fail-closed paths (e.g., EM-STRICT chain failure or PDP deny). This will prevent regressions in these newly added emission branches.
func (p *pep) handleChainError(w http.ResponseWriter, r *http.Request, err error, traceID, txnID, subjectDID, capability string) {
var envErr *envelope.Error
if errors.As(err, &envErr) {
status := ChainErrorHTTPStatus(envErr.Code)
p.logger.WarnContext(r.Context(), "authority chain verification failed",
slog.String("error_code", envErr.Code),
slog.String("error", envErr.Message),
slog.Int("status", status),
slog.String("enforcement_mode", p.config.EnforcementMode.String()),
)
// RFC-011 §5.2: Emit authority.denied on chain verification failure
p.emitAuthorityDenied(traceID, txnID, subjectDID, capability)
// In EM-OBSERVE, log but allow the request through (RFC-005 §6.3)
if p.config.EnforcementMode == pip.EMObserve {
p.logger.InfoContext(r.Context(), "chain error in EM-OBSERVE (allowing)",
slog.String("error_code", envErr.Code))
p.next.ServeHTTP(w, r)
return
- Remove old non-WithState handler functions (handleBreakGlass, evaluatePolicy, handleCachedDecision, handlePDPUnavailable, handlePDPDeny, enforceObligations) now that serveHTTP uses the WithState variants exclusively - Extract verifyBadge helper to reduce serveHTTP complexity from 16 to under 15 (gocyclo limit)
- Fix: Only set execState.aborted when enforcement mode actually blocks. In EM-OBSERVE mode, chain errors allow the request through, so we should NOT emit execution.aborted (it's not an abort). - Rename misleading test: TestEventEmission_ExecutionAborted_BadgeVerificationFailed → TestEventEmission_IdentityInvalid_BadgeVerificationFailed (reflects what the test actually verifies)
Summary
Wires the RFC-011 AsyncEmitter into the gateway PEP middleware to emit runtime events at key points in the request lifecycle.
Changes
pkg/gateway/middleware.goRuntimeEmitter *mediation.AsyncEmitterfield toPEPConfigemitterfield topepstruct and wire inNewPolicyMiddlewareidentity.verifiedevent after successful badge verificationidentity.invalidevent on badge verification failure (missing badge, verification error)authority.granted/authority.deniedevents on chain verificationexecution.startedandexecution.completedevents for request lifecycleemitIdentityVerified()- Emit identity verification successemitIdentityInvalid()- Emit identity verification failureemitAuthorityGranted()- Emit authority grant after chain verificationemitAuthorityDenied()- Emit authority denialemitExecutionStarted()- Emit execution start with request contextemitExecutionCompleted()- Emit execution completion with statusparseTrustLevel()- Convert string trust level to intparseIAL()- Convert string IAL to intpkg/gateway/event_emission_test.go(NEW)TestEventEmission_IdentityVerified- Verify identity.verified emitted on valid badgeTestEventEmission_IdentityInvalid_MissingBadge- Verify identity.invalid emitted when badge missingTestEventEmission_ExecutionLifecycle- Verify execution.started/completed eventsTestEventEmission_NoEmitter- Verify middleware works silently with nil emitterRFC Compliance
Testing
All 4 new tests pass. Full gateway test suite passes.
Implements
P1 of the Verification Locality plan - Gateway Event Integration
Follows