diff --git a/conformance/guard-core-spec-4.1.0/cases/event_stream.json b/conformance/guard-core-spec-4.1.0/cases/event_stream.json
index 65dfbe5..49f6e19 100644
--- a/conformance/guard-core-spec-4.1.0/cases/event_stream.json
+++ b/conformance/guard-core-spec-4.1.0/cases/event_stream.json
@@ -17,7 +17,27 @@
"body": ""
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "penetration_attempt",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Penetration attempt detected: Request body: Value matched pattern '"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "penetration_attempt",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "logged_only",
+ "reason": "Suspicious pattern detected (passive mode): Request body: Value matched pattern '"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "ip_blocked",
+ "ip_address": "203.0.113.13",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "IP 203.0.113.13 not in global allowlist/blocklist",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "ip_address": "203.0.113.13",
+ "filter_type": "global"
+ }
+ },
+ {
+ "event_type": "ip_ban_failed",
+ "ip_address": "203.0.113.13",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "ban_not_applied",
+ "reason": "Escalation ban failed for 203.0.113.13: corpus injected ban failure",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "ip_address": "203.0.113.13"
+ }
+ }
+ ]
},
{
"id": "evt_ip_unbanned",
@@ -109,7 +246,44 @@
"ip": "203.0.113.14"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "ip_banned",
+ "ip_address": "203.0.113.14",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "banned",
+ "reason": "corpus_ban",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "ip_ban",
+ "metadata": {
+ "duration": 3600
+ }
+ },
+ {
+ "event_type": "ip_unbanned",
+ "ip_address": "203.0.113.14",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "unbanned",
+ "reason": "dynamic_rule_whitelist",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "ip_ban",
+ "metadata": {
+ "action": "unban"
+ }
+ }
+ ]
},
{
"id": "evt_cloud_blocked_provider",
@@ -133,7 +307,49 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "cloud_blocked",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "IP belongs to blocked cloud provider: AWS",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "cloud",
+ "metadata": {
+ "cloud_provider": "AWS",
+ "network": "203.0.113.0/24"
+ }
+ },
+ {
+ "event_type": "decorator_violation",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Cloud provider IP 203.0.113.10 blocked",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": "block_clouds",
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "decorator_type": "block_clouds",
+ "violation_type": "cloud_provider",
+ "blocked_providers": [
+ "AWS"
+ ]
+ }
+ }
+ ]
},
{
"id": "evt_https_enforced_redirect",
@@ -148,7 +364,27 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "https_enforced",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "https_redirect",
+ "reason": "HTTP request redirected to HTTPS for security",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "original_scheme": "http",
+ "redirect_url": "https://example.com"
+ }
+ }
+ ]
},
{
"id": "evt_decorator_violation_auth_required",
@@ -165,7 +401,28 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "decorator_violation",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Missing or invalid Bearer token",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": "authentication",
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "decorator_type": "authentication",
+ "violation_type": "require_auth",
+ "auth_type": "bearer"
+ }
+ }
+ ]
},
{
"id": "evt_behavioral_violation_usage_log",
@@ -184,7 +441,29 @@
"details": "Usage threshold exceeded: 5 calls in 60s"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "behavioral_violation",
+ "ip_address": "203.0.113.15",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "log",
+ "reason": "Behavioral rule violated: Usage threshold exceeded: 5 calls in 60s",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": "usage",
+ "pattern_matched": null,
+ "handler_name": "behavior",
+ "metadata": {
+ "endpoint": "corpus.endpoint",
+ "rule_type": "usage",
+ "threshold": 5,
+ "window": 60
+ }
+ }
+ ]
},
{
"id": "evt_pattern_detected_xss",
@@ -199,7 +478,39 @@
"context": "request_body"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "pattern_detected",
+ "ip_address": "203.0.113.7",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "threat_detected",
+ "reason": "Threat detected in request_body",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": "",
+ "threat_score": 1.0,
+ "threats": 1,
+ "regex_threats": 1,
+ "semantic_threats": 0,
+ "timeouts": 0,
+ "detection_method": "enhanced",
+ "correlation_id": null,
+ "threat_categories": [
+ "xss"
+ ],
+ "category": "xss"
+ }
+ }
+ ]
},
{
"id": "evt_dynamic_rules_update_apply",
@@ -219,7 +530,50 @@
}
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "dynamic_rule_updated",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "rules_received",
+ "reason": "Received updated rules corpus-rules v7",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "dynamic_rules",
+ "metadata": {
+ "rule_id": "corpus-rules",
+ "version": 7,
+ "previous_version": 0
+ }
+ },
+ {
+ "event_type": "dynamic_rule_applied",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "rules_updated",
+ "reason": "Applied dynamic rules corpus-rules v7",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "dynamic_rules",
+ "metadata": {
+ "rule_id": "corpus-rules",
+ "version": 7,
+ "ip_bans": 0,
+ "country_blocks": 0,
+ "emergency_mode": false
+ }
+ }
+ ]
},
{
"id": "evt_emergency_mode_activated",
@@ -243,7 +597,71 @@
}
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "dynamic_rule_updated",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "rules_received",
+ "reason": "Received updated rules corpus-rules v9",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "dynamic_rules",
+ "metadata": {
+ "rule_id": "corpus-rules",
+ "version": 9,
+ "previous_version": 0
+ }
+ },
+ {
+ "event_type": "emergency_mode_activated",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "emergency_lockdown",
+ "reason": "[EMERGENCY MODE] activated via dynamic rules",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "dynamic_rules",
+ "metadata": {
+ "whitelist_count": 1,
+ "whitelist": [
+ "198.51.100.5"
+ ]
+ }
+ },
+ {
+ "event_type": "dynamic_rule_applied",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "rules_updated",
+ "reason": "Applied dynamic rules corpus-rules v9",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "dynamic_rules",
+ "metadata": {
+ "rule_id": "corpus-rules",
+ "version": 9,
+ "ip_bans": 0,
+ "country_blocks": 0,
+ "emergency_mode": true
+ }
+ }
+ ]
},
{
"id": "evt_access_denied_decorator",
@@ -263,7 +681,27 @@
}
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "access_denied",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "blocked",
+ "reason": "corpus access denied",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": "access_control",
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "decorator_type": "access_control",
+ "violation_type": "corpus"
+ }
+ }
+ ]
},
{
"id": "evt_authentication_failed_decorator",
@@ -282,7 +720,27 @@
}
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "authentication_failed",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "blocked",
+ "reason": "corpus auth failed",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": "authentication",
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "decorator_type": "authentication",
+ "auth_type": "bearer"
+ }
+ }
+ ]
},
{
"id": "evt_content_filtered_max_size",
@@ -301,7 +759,27 @@
"body": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "content_filtered",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Request size 64 exceeds limit: 10",
+ "endpoint": "/upload",
+ "method": "POST",
+ "status_code": null,
+ "decorator_type": "content_filtering",
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "decorator_type": "content_filtering",
+ "violation_type": "max_request_size"
+ }
+ }
+ ]
},
{
"id": "evt_country_blocked_geo",
@@ -318,7 +796,27 @@
]
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "country_blocked",
+ "ip_address": "198.51.100.9",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Country CN is blocked",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": "country_blacklist",
+ "pattern_matched": null,
+ "handler_name": "ipinfo",
+ "metadata": {
+ "country": "CN",
+ "rule_type": "country_blacklist"
+ }
+ }
+ ]
},
{
"id": "evt_csp_violation_report",
@@ -337,7 +835,30 @@
}
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "csp_violation",
+ "ip_address": "",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "logged",
+ "reason": "",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "security_headers",
+ "metadata": {
+ "document_uri": "https://example.com/page",
+ "violated_directive": "script-src",
+ "blocked_uri": "https://evil.example/x.js",
+ "source_file": "None",
+ "line_number": null
+ }
+ }
+ ]
},
{
"id": "evt_custom_request_check_reject",
@@ -352,7 +873,27 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "custom_request_check",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Custom request check returned blocking response",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "response_status": 418,
+ "check_function": "corpus_reject_all"
+ }
+ }
+ ]
},
{
"id": "evt_decoding_error_unreachable",
@@ -377,7 +918,27 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "emergency_mode_block",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "[EMERGENCY MODE] IP 203.0.113.10 not in whitelist",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "emergency_whitelist_count": 0,
+ "emergency_active": true
+ }
+ }
+ ]
},
{
"id": "evt_geo_lookup_failed_download",
@@ -389,7 +950,24 @@
"call": "geo_download_failure"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "geo_lookup_failed",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "database_download_failed",
+ "reason": "Failed to download IPInfo database: RuntimeError",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "ipinfo",
+ "metadata": {}
+ }
+ ]
},
{
"id": "evt_path_excluded_health",
@@ -407,7 +985,29 @@
"url_path": "/health"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "path_excluded",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "security_checks_bypassed",
+ "reason": "Path /health excluded from security checks",
+ "endpoint": "/health",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "excluded_path": "/health",
+ "configured_exclusions": [
+ "/health"
+ ]
+ }
+ }
+ ]
},
{
"id": "evt_pattern_added_custom",
@@ -420,7 +1020,28 @@
"pattern": "corpuspattern-[a-z]+"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "pattern_added",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "pattern_added",
+ "reason": "Custom pattern added to detection system",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "sus_patterns",
+ "metadata": {
+ "pattern": "corpuspattern-[a-z]+",
+ "pattern_type": "custom",
+ "total_patterns": 1
+ }
+ }
+ ]
},
{
"id": "evt_pattern_removed_custom",
@@ -433,7 +1054,28 @@
"pattern": "corpuspattern-[a-z]+"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "pattern_removed",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "pattern_removed",
+ "reason": "Custom pattern removed from detection system",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "sus_patterns",
+ "metadata": {
+ "pattern": "corpuspattern-[a-z]+",
+ "pattern_type": "custom",
+ "total_patterns": 0
+ }
+ }
+ ]
},
{
"id": "evt_rate_limited_global",
@@ -453,7 +1095,28 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "rate_limited",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Rate limit exceeded: 2 requests in 60s window",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "rate_limit",
+ "metadata": {
+ "request_count": 2,
+ "rate_limit": 1,
+ "window": 60
+ }
+ }
+ ]
},
{
"id": "evt_dynamic_rule_violation_endpoint",
@@ -477,7 +1140,49 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "rate_limited",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Rate limit exceeded: 2 requests in 60s window",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "rate_limit",
+ "metadata": {
+ "request_count": 2,
+ "rate_limit": 10,
+ "window": 60
+ }
+ },
+ {
+ "event_type": "dynamic_rule_violation",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Endpoint-specific rate limit exceeded: 1 requests per 60s for /api",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": "endpoint_rate_limit",
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "rule_type": "endpoint_rate_limit",
+ "endpoint": "/api",
+ "rate_limit": 1,
+ "window": 60
+ }
+ }
+ ]
},
{
"id": "evt_rate_limit_script_reloaded",
@@ -491,7 +1196,24 @@
"url_path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "rate_limit_script_reloaded",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "script_reloaded",
+ "reason": "NOSCRIPT recovery: Lua script re-cached on Redis",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "rate_limit",
+ "metadata": {}
+ }
+ ]
},
{
"id": "evt_redis_connection_established",
@@ -503,7 +1225,26 @@
"call": "redis_connect"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "redis_connection",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "connection_established",
+ "reason": "Redis connection successfully established",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "redis",
+ "metadata": {
+ "redis_url": "redis://localhost:6379/0"
+ }
+ }
+ ]
},
{
"id": "evt_redis_error_connection_failed",
@@ -515,7 +1256,27 @@
"call": "redis_connect_error"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "redis_error",
+ "ip_address": "system",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "connection_failed",
+ "reason": "Redis connection failed: Error Multiple exceptions: [Errno 61] Connect call failed ('::1', 59999, 0, 0), [Errno 61] Connect call failed ('127.0.0.1', 59999) connecting to localhost:59999.",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "redis",
+ "metadata": {
+ "redis_url": "redis://localhost:59999/0",
+ "error_type": "connection_error"
+ }
+ }
+ ]
},
{
"id": "evt_route_unresolved_strict",
@@ -531,7 +1292,24 @@
"guard_route_unresolved": true
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "route_unresolved",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "request_blocked",
+ "reason": "Route resolution failed; per-route decorator config could not be applied",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {}
+ }
+ ]
},
{
"id": "evt_security_bypass_all",
@@ -551,7 +1329,29 @@
"url_path": "/open"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "security_bypass",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "all_checks_bypassed",
+ "reason": "Route configured to bypass all security checks",
+ "endpoint": "/open",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "bypassed_checks": [
+ "all"
+ ],
+ "endpoint": "/open"
+ }
+ }
+ ]
},
{
"id": "evt_security_headers_applied_default",
@@ -564,7 +1364,29 @@
"path": "/api"
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "security_headers_applied",
+ "ip_address": "",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "headers_added",
+ "reason": "",
+ "endpoint": null,
+ "method": null,
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "security_headers",
+ "metadata": {
+ "path": "/api",
+ "headers_count": 11,
+ "has_csp": false,
+ "has_hsts": true
+ }
+ }
+ ]
},
{
"id": "evt_user_agent_blocked_global",
@@ -584,7 +1406,27 @@
}
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "user_agent_blocked",
+ "ip_address": "203.0.113.10",
+ "country": null,
+ "user_agent": "evilbot/1.0",
+ "action_taken": "request_blocked",
+ "reason": "User agent 'evilbot/1.0' in global blocklist",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "middleware",
+ "metadata": {
+ "user_agent": "evilbot/1.0",
+ "filter_type": "global"
+ }
+ }
+ ]
},
{
"id": "evt_suspicious_request_untrusted_proxy",
@@ -605,7 +1447,24 @@
"drop_cached_client_ip": true
}
],
- "expected": []
+ "expected": [
+ {
+ "event_type": "suspicious_request",
+ "ip_address": "203.0.113.17",
+ "country": null,
+ "user_agent": null,
+ "action_taken": "spoofing_detected",
+ "reason": "Potential IP spoof attempt: X-Forwarded-For header 198.51.100.77",
+ "endpoint": "/api",
+ "method": "GET",
+ "status_code": null,
+ "decorator_type": null,
+ "rule_type": null,
+ "pattern_matched": null,
+ "handler_name": "ip_extraction",
+ "metadata": {}
+ }
+ ]
},
{
"id": "evt_detection_engine_callback_error",
diff --git a/conformance/guard-core-spec-4.1.0/cases/index.json b/conformance/guard-core-spec-4.1.0/cases/index.json
index ccf5a4d..c0ea0d5 100644
--- a/conformance/guard-core-spec-4.1.0/cases/index.json
+++ b/conformance/guard-core-spec-4.1.0/cases/index.json
@@ -1,8 +1,8 @@
{
"spec_version": "4.1.0",
"engine_version": "4.3.2",
- "engine_commit": "58957987b677d1e06930347443df7c8394758ea7",
- "generated_at": "2026-10-10T14:39:35.908173+00:00",
+ "engine_commit": "37b6a132b7df974f0ea014f3100fd90a76e1ebc5",
+ "generated_at": "2026-10-10T16:34:12.919136+00:00",
"fixed_ip": "203.0.113.7",
"config_knobs": {
"detection_compiler_timeout": 2.0,
@@ -275,22 +275,22 @@
"k": 5,
"floor_ms": 250,
"timing_runs": 5,
- "generated_at": "2026-10-10T14:40:11+00:00",
+ "generated_at": "2026-10-10T16:34:48+00:00",
"budgets": {
"cost_prose_8kib": {
- "reference_ms": 26.8,
+ "reference_ms": 26.6,
"expected_is_threat": false
},
"cost_prose_64kib": {
- "reference_ms": 182.2,
+ "reference_ms": 184.6,
"expected_is_threat": false
},
"cost_prose_256kib": {
- "reference_ms": 717.9,
+ "reference_ms": 708.8,
"expected_is_threat": false
},
"cost_prose_1mib": {
- "reference_ms": 1238.6,
+ "reference_ms": 1227.2,
"expected_is_threat": false
},
"cost_encoded_64kib": {
@@ -298,19 +298,19 @@
"expected_is_threat": false
},
"cost_binnoise_64kib": {
- "reference_ms": 205.8,
+ "reference_ms": 200.6,
"expected_is_threat": false
},
"cost_threat_8kib": {
- "reference_ms": 26.9,
+ "reference_ms": 26.4,
"expected_is_threat": true
},
"cost_threat_tail_256kib": {
- "reference_ms": 714.5,
+ "reference_ms": 702.6,
"expected_is_threat": true
},
"cost_threat_mid_1mib": {
- "reference_ms": 1242.5,
+ "reference_ms": 1224.9,
"expected_is_threat": false
}
}
diff --git a/conformance/guard-core-spec-4.1.0/php_events_xfail.json b/conformance/guard-core-spec-4.1.0/php_events_xfail.json
index 5df40ef..effcc1e 100644
--- a/conformance/guard-core-spec-4.1.0/php_events_xfail.json
+++ b/conformance/guard-core-spec-4.1.0/php_events_xfail.json
@@ -1,35 +1,38 @@
{
"spec_version": "4.1.0",
- "_comment": "Documented divergences of the PHP event surface against the reference event_stream corpus (comparison block events_envelopes: expected keys only, volatile fields dropped recursively, absent fields recorded as null). Fail-closed: a failing case NOT listed here is red; a listed case that now passes is red (stale baseline); a case with no driver that is NOT listed is red. Root causes: (1) the reference's handler-level emitters (sus_patterns, ip_ban envelopes, security_headers, redis manager, ip extraction, decorators, dynamic-rule violation events) are not ported or are expressed as adapter seams without envelopes; (2) PHP block events ride the check pipeline bus (SecurityCheckPipeline::fireBlockHook) so their reasons are the on_block log-format strings and their metadata is the check-hook shape (check_name/trigger_info/status_code) instead of the reference's per-event metadata; (3) SecurityCheckPipeline::blockEventType maps only the eight pipeline check families, so blocks outside that map fall to the default penetration_attempt type. 4.3.2 corpus regen note: the reference generation environment does not have the optional guard_agent module installed, and the reference engine builds every middleware-bus SecurityEvent through guard_core._pydantic_plugin_mute.get_telemetry_model('SecurityEvent'), which imports guard_agent, so the construction fails closed and the reference capture harness recorded ZERO envelopes for every bus-emitted scenario; the corpus therefore pins expected: [] for them (verified live against the reference engine, which still captures 0 events for these cases). Two consequences, both applied in this ledger update: (a) the scenarios where the port emits nothing anyway now compare 0 against 0 and PASS, so five stale entries were removed (evt_behavioral_violation_usage_log, evt_ip_unbanned, evt_path_excluded_health, evt_pattern_detected_xss, evt_security_bypass_all) - their documented port gaps are unchanged but no longer produce a diff under this corpus revision; those passes ride on the corpus artifact, and a corpus regen with guard_agent installed will pin real envelopes again and resurface them as failures needing entries; (b) the scenarios where the port DOES emit (country_blocked, geo_lookup_failed) now diverge at count level against the zero pins and gained entries. If the corpus regenerates with guard_agent installed, every entry tied to a zero pin must be re-evaluated (stale entries are red by policy).",
+ "_comment": "Documented divergences of the PHP event surface against the reference event_stream corpus (comparison block events_envelopes: expected keys only, volatile fields dropped recursively, absent fields recorded as null). Fail-closed: a failing case NOT listed here is red; a listed case that now passes is red (stale baseline); a case with no driver that is NOT listed is red. Corpus regen v2 (engine 4.3.2 generated with the optional guard_agent module installed, Guard-Core/guard-core#151) pins 47 real envelopes across 38 non-empty cases, superseding the round-1 generation-environment artifact that pinned expected: [] for the bus-emitted scenarios. Ledger re-evaluation against the real pins: the two round-1 count-level entries (evt_country_blocked_geo, evt_geo_lookup_failed_download) are REMOVED as stale by policy (the port's envelopes now byte-match the real pins); the five entries removed in round 1 (evt_behavioral_violation_usage_log, evt_ip_unbanned, evt_path_excluded_health, evt_pattern_detected_xss, evt_security_bypass_all) RETURN because their documented port gaps produce real got-0-want-N divergences against the real pins; the remaining entries are re-reasoned from the round-1 count-level artifact wording to the actual envelope divergence (missing emitters, pipeline block-mapping shapes, and the trailing security_headers_applied envelope the port's pipeline emits and the reference does not). Root causes: (1) the reference's handler-level emitters (sus_patterns, ip_ban, behavioral_violation, security_headers, redis manager, ip extraction, csp report, several decorators) are not ported or are expressed as adapter seams without envelopes; (2) PHP block events ride the check pipeline bus (SecurityCheckPipeline::fireBlockHook), so their reasons are the on_block log-format strings and their metadata is the check-hook shape instead of the reference's per-event metadata; (3) SecurityCheckPipeline::blockEventType maps only the eight pipeline check families, so blocks outside that map fall to the default penetration_attempt type.",
"cases": {
- "evt_penetration_attempt_blocked": "The PHP pipeline classifies the body XSS block under the suspicious_activity check, so SecurityCheckPipeline::blockEventType maps it to suspicious_request with the check-hook metadata shape (fireBlockHook); the reference emits penetration_attempt with request_count/trigger_info metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (suspicious_request, security_headers_applied).",
- "evt_penetration_attempt_passive": "PHP passive mode returns the request without a block response, so SecurityCheckPipeline::fireBlockHook never runs and the bus emits nothing for the block; the reference's passive dispatch emits a logged_only penetration_attempt. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 1 (security_headers_applied).",
- "evt_ip_blocked_global_blacklist": "The PHP bus reason is the composed log format 'IP not allowed: - ' and the metadata is the check-hook shape (SecurityCheckPipeline::fireBlockHook); the reference envelope carries the bare detail reason with filter_type metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (ip_blocked, security_headers_applied).",
- "evt_ip_banned_then_blocked": "The engine emits only the ip_blocked envelope: the ban-time event is an adapter-authored BanEventSink call (src/Ban/BanEventSink.php) carrying just (ip, duration, reason), so the corpus ip_banned envelope (handler ip_ban, metadata.duration) has no in-engine emitter. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (ip_blocked, security_headers_applied).",
+ "evt_penetration_attempt_blocked": "The PHP pipeline classifies the body XSS block under the suspicious_activity check, so SecurityCheckPipeline::blockEventType maps it to suspicious_request with the check-hook metadata shape (fireBlockHook), and the pipeline appends a trailing security_headers_applied envelope; the reference pins a single penetration_attempt envelope with request_count/trigger_info metadata (got 2, want 1).",
+ "evt_penetration_attempt_passive": "PHP passive mode returns the request without a block response, so SecurityCheckPipeline::fireBlockHook never runs and the bus emits only the headers-applied envelope (event_type security_headers_applied, empty ip, action headers_added); the reference's passive dispatch emits a logged_only penetration_attempt carrying the client IP.",
+ "evt_ip_blocked_global_blacklist": "The PHP bus reason is the composed log format 'IP not allowed: - ' and the metadata is the check-hook shape (SecurityCheckPipeline::fireBlockHook), and the pipeline appends a trailing security_headers_applied envelope; the reference pins a single ip_blocked envelope with the bare detail reason and filter_type metadata (got 2, want 1).",
+ "evt_ip_banned_then_blocked": "The engine emits only the ip_blocked envelope: the ban-time event is an adapter-authored BanEventSink call (src/Ban/BanEventSink.php) carrying just (ip, duration, reason), so the reference's leading ip_banned envelope (handler ip_ban, metadata.duration, action banned) has no in-engine emitter, and the pipeline appends a trailing security_headers_applied envelope.",
"evt_ip_ban_failed_escalation": "No PHP seam: the corpus ipban_fault injection has no analogue (IpBanManager has no raisable ban hook), and no PHP source emits ip_ban_failed anywhere; the runner's driver throws for the corpus call.",
- "evt_cloud_blocked_provider": "The pipeline emits the cloud_blocked envelope but no decorator_violation: route-level decorator events (handler middleware, decorator_type block_clouds) are not ported; the port's cloud block surfaces only through the check pipeline. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 4 (cloud_blocked, decorator_violation, cloud_blocked, security_headers_applied).",
- "evt_https_enforced_redirect": "EventBus::sendHttpsViolationEvent builds the https_enforced envelope but no engine code calls it: the enforce_https redirect returns through the check pipeline, so the emit falls to the default block mapping (penetration_attempt) with check-hook metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (penetration_attempt, security_headers_applied).",
- "evt_decorator_violation_auth_required": "No PHP seam for the decorator send_authentication_failed_event emitter; the PHP authentication_failed envelope comes only from the pipeline block mapping, which cannot carry injected decorator kwargs; the runner's driver throws for the corpus decorator_event call.",
+ "evt_ip_unbanned": "No in-engine emitter: the unban-time event is an adapter-authored BanEventSink call (src/Ban/BanEventSink.php) with no envelope, so the port emits 0 envelopes where the corpus pins 2 (ip_banned, ip_unbanned).",
+ "evt_behavioral_violation_usage_log": "Documented port gap (src/Behavior/BehaviorTracker.php, applyAction): the reference's behavioral_violation agent event is not modeled; the action dispatch logs only, so the port emits 0 envelopes where the corpus pins 1 (behavioral_violation).",
+ "evt_cloud_blocked_provider": "The port emits 4 envelopes (cloud_blocked, decorator_violation, cloud_blocked, security_headers_applied) where the reference pins 2 (cloud_blocked, decorator_violation): the pipeline surfaces the cloud block twice and appends the trailing headers-applied envelope the reference does not emit.",
+ "evt_https_enforced_redirect": "EventBus::sendHttpsViolationEvent builds the https_enforced envelope but no engine code calls it: the enforce_https redirect returns through the check pipeline, so the block falls to the default penetration_attempt mapping and the pipeline appends security_headers_applied; the reference pins a single https_enforced envelope (got 2, want 1).",
+ "evt_decorator_violation_auth_required": "The corpus decorator drive runs through the check pipeline, which maps the auth-required block to authentication_failed with check-hook metadata and appends security_headers_applied; the reference pins a single decorator_violation envelope carrying the injected decorator kwargs, which the pipeline block mapping cannot carry (got 2, want 1).",
"evt_access_denied_decorator": "No PHP seam: the reference BaseSecurityDecorator send_access_denied_event emitter has no port; the bus carries only the require_https decorator pair; the runner's driver throws for the corpus decorator_event call.",
"evt_authentication_failed_decorator": "No PHP seam for the decorator send_authentication_failed_event emitter; the PHP authentication_failed envelope comes only from the pipeline block mapping, which cannot carry injected decorator kwargs; the runner's driver throws for the corpus decorator_event call.",
- "evt_content_filtered_max_size": "The request_size_content check blocks the oversized request with the reference reason ('Request size 64 exceeds limit: 10'), but SecurityCheckPipeline::blockEventType has no content-filtering mapping (the block falls to the default penetration_attempt) and no emitter carries the content_filtered decorator metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (penetration_attempt, security_headers_applied).",
- "evt_country_blocked_geo": "Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes for this scenario because the reference generation environment lacks the optional guard_agent module (reference bus event construction fails closed, so the capture harness recorded zero); the port's geo_country_stub driver emits 1 country_blocked envelope through its own bus, so the runner sees got 1 want 0.",
- "evt_geo_lookup_failed_download": "Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes for this scenario because the reference generation environment lacks the optional guard_agent module (reference bus event construction fails closed, so the capture harness recorded zero); the port's geo_download_failure driver emits 1 geo_lookup_failed envelope through its own bus, so the runner sees got 1 want 0.",
+ "evt_content_filtered_max_size": "The request_size_content check blocks the oversized request with the reference reason ('Request size 64 exceeds limit: 10'), but SecurityCheckPipeline::blockEventType has no content-filtering mapping (the block falls to the default penetration_attempt) and the pipeline appends security_headers_applied; the reference pins a single content_filtered envelope with decorator metadata (got 2, want 1).",
"evt_csp_violation_report": "No PHP seam: SecurityHeadersPolicy computes header maps only; the reference security_headers_manager's validate_csp_report / csp_violation agent event is not ported; the runner's driver throws for the corpus csp_report call.",
- "evt_custom_request_check_reject": "The custom_request check blocks with the injected 418 response, but SecurityCheckPipeline::blockEventType maps custom_request to the default penetration_attempt and the bus reason is the generic 'Request blocked'; the reference emits custom_request_check with response_status/check_function metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (penetration_attempt, security_headers_applied).",
- "evt_emergency_mode_block": "The PHP emergency_mode check reason is the log-format '[EMERGENCY MODE] Access denied for IP ' with check-hook metadata; the reference envelope reason is '[EMERGENCY MODE] IP not in whitelist' with emergency metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (emergency_mode_block, security_headers_applied).",
+ "evt_custom_request_check_reject": "The custom_request check blocks with the injected 418 response, but SecurityCheckPipeline::blockEventType maps custom_request to the default penetration_attempt and the bus reason is the generic 'Request blocked', and the pipeline appends security_headers_applied; the reference pins a single custom_request_check envelope with response_status/check_function metadata (got 2, want 1).",
+ "evt_emergency_mode_block": "The PHP emergency_mode check reason is the log-format '[EMERGENCY MODE] Access denied for IP ' with check-hook metadata, and the pipeline appends a trailing security_headers_applied envelope; the reference pins a single emergency_mode_block envelope with reason '[EMERGENCY MODE] IP not in whitelist' and emergency metadata (got 2, want 1).",
+ "evt_path_excluded_health": "Excluded paths skip the request silently through the pipeline's exclusion scope; no path_excluded emitter exists in the port, which emits 0 envelopes where the corpus pins 1 (path_excluded).",
+ "evt_pattern_detected_xss": "SusPatterns::detect returns the detection result and emits no agent envelope; the reference sus_patterns_handler's pattern_detected emitter has no PHP analogue, so the port emits 0 envelopes where the corpus pins 1 (pattern_detected).",
"evt_pattern_added_custom": "No PHP seam: SusPatterns carries no custom-pattern registry surface, so no pattern_added envelope exists; the runner's driver throws for the corpus add_pattern call.",
"evt_pattern_removed_custom": "No PHP seam: SusPatterns carries no custom-pattern registry surface, so no pattern_removed envelope exists; the runner's driver throws for the corpus remove_pattern call.",
- "evt_rate_limited_global": "The PHP rate-limit block reason is the composed log format 'Rate limit exceeded for IP: (N requests in Ws window)' and the emit rides the middleware handler with check-hook metadata; the reference envelope carries the bare 'Rate limit exceeded: ...' reason with a rate_limit handler and request_count metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (security_headers_applied, rate_limited).",
- "evt_dynamic_rule_violation_endpoint": "The PHP endpoint-tier block emits rate_limited only; the reference's dynamic_rule_violation middleware emit for endpoint-specific limits has no PHP emitter. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (security_headers_applied, rate_limited).",
+ "evt_rate_limited_global": "The PHP rate-limit block reason is the composed log format 'Rate limit exceeded for IP: (N requests in Ws window)' and the emit rides the middleware handler with check-hook metadata, led by the trailing security_headers_applied envelope; the reference pins a single rate_limited envelope with the bare 'Rate limit exceeded: ...' reason, a rate_limit handler and request_count metadata (got 2, want 1).",
+ "evt_dynamic_rule_violation_endpoint": "The port emits the trailing security_headers_applied envelope ahead of rate_limited and has no dynamic_rule_violation middleware emit for endpoint-specific limits; the reference pins a single rate_limited envelope carrying the client IP and action request_blocked (envelope[0] got security_headers_applied, want rate_limited).",
"evt_rate_limit_script_reloaded": "No envelope seam: the RateLimitHandler NOSCRIPT recovery reloads the script silently (src/RateLimit/RateLimitHandler.php) and its onScriptReloaded callback carries no envelope; the reference's rate_limit_script_reloaded agent event is not emitted; the runner's driver throws for the corpus rate_limit_script_reload call.",
- "evt_dynamic_rules_update_apply": "DynamicRuleManager::sendRuleEvent emits through the middleware bus (handler middleware, reason 'Dynamic rules v', method SYSTEM), not the reference dynamic_rules handler envelope (reason 'Received/Applied updated rules ...', ip system, previous_version metadata). Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (dynamic_rule_updated, dynamic_rule_applied).",
- "evt_emergency_mode_activated": "The PHP DynamicRuleManager emits dynamic_rule_updated/applied but has no emergency_mode_activated emitter; the emergency lockdown path logs only. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (dynamic_rule_updated, dynamic_rule_applied).",
+ "evt_dynamic_rules_update_apply": "DynamicRuleManager::sendRuleEvent emits through the middleware bus (handler middleware, method SYSTEM, non-null endpoint) while the reference pins the dynamic_rules handler envelope shape (reason 'Received updated rules v', null method and endpoint): envelope[0] reason got 'Dynamic rules corpus-rules v7' want 'Received updated rules corpus-rules v7'; the applied half of the pin matches.",
+ "evt_emergency_mode_activated": "The PHP DynamicRuleManager emits dynamic_rule_updated/applied but has no emergency_mode_activated emitter; the emergency lockdown path logs only, so the middle envelope of the reference's 3-envelope pin (dynamic_rule_updated, emergency_mode_activated, dynamic_rule_applied) is missing (got 2, want 3).",
"evt_redis_connection_established": "No PHP seam: the port's RedisHandler has no manager-level agent surface; the reference RedisManager emits redis_connection from initialize; the runner's driver throws for the corpus redis_connect call.",
"evt_redis_error_connection_failed": "No PHP seam: the port's RedisHandler has no manager-level agent surface; the reference RedisManager emits redis_error from a failed initialize; the runner's driver throws for the corpus redis_connect_error call.",
- "evt_route_unresolved_strict": "No PHP emitter: routeResolutionStrict parses into SecurityConfig but no engine code consumes it, so the reference's route_unresolved emit has no analogue. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 1 (security_headers_applied).",
+ "evt_route_unresolved_strict": "No PHP emitter: routeResolutionStrict parses into SecurityConfig but no engine code consumes it, so the bus carries only the headers-applied envelope where the reference pins a single route_unresolved envelope with the client IP and action request_blocked.",
+ "evt_security_bypass_all": "PHP bypassed_checks short-circuits the pipeline silently; no security_bypass emitter exists in the port, which emits 0 envelopes where the corpus pins 1 (security_bypass).",
"evt_security_headers_applied_default": "No driver and no emitter: the port's SecurityHeadersPolicy computes headers on demand with no agent surface (KNOWN_GAPS, security headers management); the reference's headers_applied event is not emitted.",
- "evt_user_agent_blocked_global": "The PHP user-agent block reason is the composed log format 'Blocked user agent: ' with check-hook metadata; the reference envelope reason is \"User agent '' in global blocklist\" with filter_type metadata. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 2 (user_agent_blocked, security_headers_applied).",
- "evt_suspicious_request_untrusted_proxy": "No PHP seam: extracting a client IP behind an untrusted proxy emits nothing in the port; the reference's ip_extraction spoofing seam in extract_client_ip has no analogue. Under the 4.3.2 corpus the divergence is count-level: the corpus pins 0 envelopes (reference bus event construction fails closed without the optional guard_agent module in the generation environment) while the port emits 1 (security_headers_applied)."
+ "evt_user_agent_blocked_global": "The PHP user-agent block reason is the composed log format 'Blocked user agent: ' with check-hook metadata, and the pipeline appends a trailing security_headers_applied envelope; the reference pins a single user_agent_blocked envelope with reason \"User agent '' in global blocklist\" and filter_type metadata (got 2, want 1).",
+ "evt_suspicious_request_untrusted_proxy": "No PHP seam: extracting a client IP behind an untrusted proxy emits nothing in the port; the bus carries only the headers-applied envelope where the reference pins a single suspicious_request envelope (ip 203.0.113.17, action spoofing_detected)."
}
}