nat-engine 1__py3-none-any.whl
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.
- mannf/__init__.py +33 -0
- mannf/__main__.py +10 -0
- mannf/_version.py +8 -0
- mannf/agents/__init__.py +7 -0
- mannf/agents/analyzer_agent.py +9 -0
- mannf/agents/base.py +9 -0
- mannf/agents/bdi_agent.py +9 -0
- mannf/agents/belief_state.py +9 -0
- mannf/agents/coordinator_agent.py +9 -0
- mannf/agents/executor_agent.py +9 -0
- mannf/agents/monitor_agent.py +9 -0
- mannf/agents/oracle_agent.py +9 -0
- mannf/agents/planner_agent.py +9 -0
- mannf/agents/test_agent.py +9 -0
- mannf/anomaly/__init__.py +7 -0
- mannf/anomaly/enhanced_detector.py +9 -0
- mannf/cli.py +9 -0
- mannf/core/__init__.py +26 -0
- mannf/core/agents/__init__.py +52 -0
- mannf/core/agents/accessibility_scanner_agent.py +245 -0
- mannf/core/agents/analyzer_agent.py +224 -0
- mannf/core/agents/autonomous_loop_agent.py +1086 -0
- mannf/core/agents/autonomous_loop_models.py +62 -0
- mannf/core/agents/autonomous_run_differ.py +427 -0
- mannf/core/agents/base.py +128 -0
- mannf/core/agents/bdi_agent.py +330 -0
- mannf/core/agents/belief_state.py +202 -0
- mannf/core/agents/browser_coordinator_agent.py +224 -0
- mannf/core/agents/browser_executor_agent.py +410 -0
- mannf/core/agents/coordinator_agent.py +262 -0
- mannf/core/agents/executor_agent.py +222 -0
- mannf/core/agents/monitor_agent.py +188 -0
- mannf/core/agents/oracle_agent.py +150 -0
- mannf/core/agents/performance_testing_agent.py +279 -0
- mannf/core/agents/planner_agent.py +128 -0
- mannf/core/agents/test_agent.py +249 -0
- mannf/core/agents/visual_regression_agent.py +311 -0
- mannf/core/agents/web_crawler_agent.py +510 -0
- mannf/core/agents/worker_pool.py +366 -0
- mannf/core/anomaly/__init__.py +14 -0
- mannf/core/anomaly/enhanced_detector.py +541 -0
- mannf/core/browser/__init__.py +63 -0
- mannf/core/browser/accessibility_scanner.py +424 -0
- mannf/core/browser/discovery_model.py +178 -0
- mannf/core/browser/dom_snapshot.py +349 -0
- mannf/core/browser/ingestor_bridge.py +371 -0
- mannf/core/browser/performance_metrics.py +217 -0
- mannf/core/browser/reflection_analyzer.py +442 -0
- mannf/core/browser/scenario_generator.py +1100 -0
- mannf/core/browser/security_scenario_generator.py +695 -0
- mannf/core/browser/visual_comparer.py +159 -0
- mannf/core/diagnostics/__init__.py +28 -0
- mannf/core/diagnostics/failure_clusterer.py +211 -0
- mannf/core/diagnostics/flake_detector.py +233 -0
- mannf/core/diagnostics/root_cause_analyzer.py +273 -0
- mannf/core/distributed/__init__.py +16 -0
- mannf/core/distributed/endpoint.py +139 -0
- mannf/core/distributed/system_under_test.py +207 -0
- mannf/core/functional_orchestrator.py +428 -0
- mannf/core/messaging/__init__.py +11 -0
- mannf/core/messaging/bus.py +113 -0
- mannf/core/messaging/messages.py +89 -0
- mannf/core/nat_orchestrator.py +342 -0
- mannf/core/neural/__init__.py +183 -0
- mannf/core/orchestrator.py +272 -0
- mannf/core/prioritization/__init__.py +17 -0
- mannf/core/prioritization/adaptive_controller.py +509 -0
- mannf/core/prioritization/belief_prioritizer.py +231 -0
- mannf/core/prioritization/risk_scorer.py +430 -0
- mannf/core/reporting/__init__.py +12 -0
- mannf/core/reporting/unified_report.py +664 -0
- mannf/core/testing/__init__.py +17 -0
- mannf/core/testing/adaptive_controller.py +149 -0
- mannf/core/testing/models.py +179 -0
- mannf/core/validation/__init__.py +10 -0
- mannf/core/validation/self_validation_runner.py +180 -0
- mannf/dashboard/__init__.py +7 -0
- mannf/dashboard/app.py +9 -0
- mannf/dashboard/models.py +9 -0
- mannf/dashboard/static/index.html +2538 -0
- mannf/dashboard/telemetry.py +9 -0
- mannf/distributed/__init__.py +7 -0
- mannf/distributed/endpoint.py +9 -0
- mannf/distributed/system_under_test.py +9 -0
- mannf/healing/__init__.py +7 -0
- mannf/healing/graphql_schema_diff.py +9 -0
- mannf/healing/healer.py +9 -0
- mannf/healing/models.py +9 -0
- mannf/healing/schema_diff.py +9 -0
- mannf/integrations/__init__.py +7 -0
- mannf/integrations/auth.py +9 -0
- mannf/integrations/graphql_parser.py +9 -0
- mannf/integrations/graphql_sut.py +9 -0
- mannf/integrations/http_sut.py +9 -0
- mannf/integrations/openapi_parser.py +9 -0
- mannf/integrations/postman_parser.py +9 -0
- mannf/llm/__init__.py +7 -0
- mannf/llm/anthropic_provider.py +9 -0
- mannf/llm/base.py +9 -0
- mannf/llm/config.py +9 -0
- mannf/llm/factory.py +9 -0
- mannf/llm/openai_provider.py +9 -0
- mannf/llm/prompts.py +9 -0
- mannf/messaging/__init__.py +7 -0
- mannf/messaging/bus.py +9 -0
- mannf/messaging/messages.py +9 -0
- mannf/nat_orchestrator.py +9 -0
- mannf/neural/__init__.py +7 -0
- mannf/orchestrator.py +9 -0
- mannf/prioritization/__init__.py +7 -0
- mannf/prioritization/adaptive_controller.py +9 -0
- mannf/prioritization/belief_prioritizer.py +9 -0
- mannf/prioritization/risk_scorer.py +9 -0
- mannf/product/__init__.py +29 -0
- mannf/product/admin/__init__.py +3 -0
- mannf/product/admin/routes.py +514 -0
- mannf/product/auth/__init__.py +5 -0
- mannf/product/auth/saml.py +212 -0
- mannf/product/billing/__init__.py +5 -0
- mannf/product/billing/audit.py +160 -0
- mannf/product/billing/feature_gates.py +180 -0
- mannf/product/billing/metering.py +179 -0
- mannf/product/billing/notifications.py +181 -0
- mannf/product/billing/plans.py +133 -0
- mannf/product/billing/rate_limits.py +35 -0
- mannf/product/billing/stripe_billing.py +906 -0
- mannf/product/billing/tenant_auth.py +233 -0
- mannf/product/billing/tenant_manager.py +873 -0
- mannf/product/cli.py +3900 -0
- mannf/product/cli_admin.py +408 -0
- mannf/product/dashboard/__init__.py +61 -0
- mannf/product/dashboard/app.py +3567 -0
- mannf/product/dashboard/models.py +460 -0
- mannf/product/dashboard/static/index.html +6347 -0
- mannf/product/dashboard/static/manifest.json +25 -0
- mannf/product/dashboard/static/pwa-icon-192.png +0 -0
- mannf/product/dashboard/static/pwa-icon-512.png +0 -0
- mannf/product/dashboard/static/sw.js +64 -0
- mannf/product/dashboard/telemetry.py +547 -0
- mannf/product/database.py +145 -0
- mannf/product/demo.py +844 -0
- mannf/product/doctor.py +509 -0
- mannf/product/exporters/__init__.py +65 -0
- mannf/product/exporters/azuredevops_exporter.py +257 -0
- mannf/product/exporters/base.py +307 -0
- mannf/product/exporters/bugzilla_exporter.py +200 -0
- mannf/product/exporters/dedup.py +275 -0
- mannf/product/exporters/finding_adapter.py +216 -0
- mannf/product/exporters/github_exporter.py +197 -0
- mannf/product/exporters/gitlab_exporter.py +215 -0
- mannf/product/exporters/jira_exporter.py +180 -0
- mannf/product/exporters/linear_exporter.py +195 -0
- mannf/product/exporters/loader.py +233 -0
- mannf/product/exporters/pagerduty_exporter.py +363 -0
- mannf/product/exporters/sentry_exporter.py +322 -0
- mannf/product/exporters/servicenow_exporter.py +240 -0
- mannf/product/exporters/shortcut_exporter.py +231 -0
- mannf/product/exporters/webhook_exporter.py +383 -0
- mannf/product/formatters/__init__.py +18 -0
- mannf/product/formatters/allure_formatter.py +161 -0
- mannf/product/formatters/ctrf_formatter.py +149 -0
- mannf/product/healing/__init__.py +30 -0
- mannf/product/healing/graphql_schema_diff.py +152 -0
- mannf/product/healing/healer.py +141 -0
- mannf/product/healing/models.py +175 -0
- mannf/product/healing/schema_diff.py +251 -0
- mannf/product/ingestors/__init__.py +77 -0
- mannf/product/ingestors/base.py +256 -0
- mannf/product/ingestors/bgstm_ingestor.py +764 -0
- mannf/product/ingestors/curl_ingestor.py +1019 -0
- mannf/product/ingestors/cypress_ingestor.py +487 -0
- mannf/product/ingestors/gherkin_ingestor.py +967 -0
- mannf/product/ingestors/graphql_ingestor.py +845 -0
- mannf/product/ingestors/grpc_ingestor.py +591 -0
- mannf/product/ingestors/har_ingestor.py +976 -0
- mannf/product/ingestors/loader.py +284 -0
- mannf/product/ingestors/models.py +146 -0
- mannf/product/ingestors/openapi_ingestor.py +606 -0
- mannf/product/ingestors/playwright_ingestor.py +449 -0
- mannf/product/ingestors/postman_ingestor.py +631 -0
- mannf/product/ingestors/traffic_ingestor.py +679 -0
- mannf/product/ingestors/websocket_ingestor.py +526 -0
- mannf/product/integrations/__init__.py +21 -0
- mannf/product/integrations/auth.py +190 -0
- mannf/product/integrations/graphql_parser.py +436 -0
- mannf/product/integrations/graphql_sut.py +247 -0
- mannf/product/integrations/grpc_sut.py +469 -0
- mannf/product/integrations/http_sut.py +237 -0
- mannf/product/integrations/kafka_adapter.py +342 -0
- mannf/product/integrations/openapi_parser.py +513 -0
- mannf/product/integrations/postman_parser.py +467 -0
- mannf/product/integrations/webhook_receiver.py +344 -0
- mannf/product/integrations/websocket_sut.py +434 -0
- mannf/product/llm/__init__.py +25 -0
- mannf/product/llm/anthropic_provider.py +94 -0
- mannf/product/llm/base.py +267 -0
- mannf/product/llm/config.py +48 -0
- mannf/product/llm/factory.py +42 -0
- mannf/product/llm/openai_provider.py +93 -0
- mannf/product/llm/prompts.py +403 -0
- mannf/product/llm/root_cause_service.py +311 -0
- mannf/product/llm/test_plan_models.py +78 -0
- mannf/product/metrics.py +149 -0
- mannf/product/middleware/__init__.py +3 -0
- mannf/product/middleware/audit_middleware.py +112 -0
- mannf/product/middleware/tenant_isolation.py +114 -0
- mannf/product/models.py +347 -0
- mannf/product/notifications/__init__.py +24 -0
- mannf/product/notifications/dispatcher.py +411 -0
- mannf/product/onboarding.py +190 -0
- mannf/product/orchestration/__init__.py +39 -0
- mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
- mannf/product/orchestration/pipeline.py +401 -0
- mannf/product/orchestrator.py +987 -0
- mannf/product/orchestrator_models.py +269 -0
- mannf/product/regression/__init__.py +36 -0
- mannf/product/regression/differ.py +172 -0
- mannf/product/regression/masking.py +100 -0
- mannf/product/regression/models.py +232 -0
- mannf/product/regression/recorder.py +124 -0
- mannf/product/regression/replayer.py +168 -0
- mannf/product/reports/__init__.py +10 -0
- mannf/product/reports/pdf.py +132 -0
- mannf/product/scheduling/__init__.py +57 -0
- mannf/product/scheduling/cron_utils.py +251 -0
- mannf/product/scheduling/engine.py +473 -0
- mannf/product/scheduling/models.py +86 -0
- mannf/product/scheduling/queue.py +894 -0
- mannf/product/scheduling/store.py +235 -0
- mannf/product/security/__init__.py +21 -0
- mannf/product/security/belief_guided.py +143 -0
- mannf/product/security/checks/__init__.py +55 -0
- mannf/product/security/checks/base.py +69 -0
- mannf/product/security/checks/bfla.py +77 -0
- mannf/product/security/checks/bola.py +77 -0
- mannf/product/security/checks/bopla.py +80 -0
- mannf/product/security/checks/broken_auth.py +86 -0
- mannf/product/security/checks/graphql_security.py +299 -0
- mannf/product/security/checks/inventory.py +70 -0
- mannf/product/security/checks/misconfig.py +158 -0
- mannf/product/security/checks/resource_consumption.py +70 -0
- mannf/product/security/checks/sensitive_flows.py +80 -0
- mannf/product/security/checks/ssrf.py +101 -0
- mannf/product/security/checks/unsafe_consumption.py +120 -0
- mannf/product/security/models.py +92 -0
- mannf/product/security/plugin_loader.py +182 -0
- mannf/product/security/reporter.py +92 -0
- mannf/product/security/scanner.py +183 -0
- mannf/product/server.py +6220 -0
- mannf/product/setup_wizard.py +873 -0
- mannf/product/status.py +404 -0
- mannf/product/storage/__init__.py +10 -0
- mannf/product/storage/artifact_store.py +343 -0
- mannf/product/telemetry.py +300 -0
- mannf/product/uninstall.py +169 -0
- mannf/product/upgrade.py +139 -0
- mannf/product/weights/__init__.py +13 -0
- mannf/product/weights/blob_store.py +299 -0
- mannf/product/weights/factory.py +42 -0
- mannf/product/weights/registry.py +159 -0
- mannf/product/weights/store.py +210 -0
- mannf/regression/__init__.py +7 -0
- mannf/regression/differ.py +9 -0
- mannf/regression/masking.py +9 -0
- mannf/regression/models.py +9 -0
- mannf/regression/recorder.py +9 -0
- mannf/regression/replayer.py +9 -0
- mannf/security/__init__.py +7 -0
- mannf/security/belief_guided.py +9 -0
- mannf/security/checks/__init__.py +7 -0
- mannf/security/checks/base.py +9 -0
- mannf/security/checks/bfla.py +9 -0
- mannf/security/checks/bola.py +9 -0
- mannf/security/checks/bopla.py +9 -0
- mannf/security/checks/broken_auth.py +9 -0
- mannf/security/checks/graphql_security.py +9 -0
- mannf/security/checks/inventory.py +9 -0
- mannf/security/checks/misconfig.py +9 -0
- mannf/security/checks/resource_consumption.py +9 -0
- mannf/security/checks/sensitive_flows.py +9 -0
- mannf/security/checks/ssrf.py +9 -0
- mannf/security/checks/unsafe_consumption.py +9 -0
- mannf/security/models.py +9 -0
- mannf/security/reporter.py +9 -0
- mannf/security/scanner.py +9 -0
- mannf/server.py +9 -0
- mannf/testing/__init__.py +7 -0
- mannf/testing/adaptive_controller.py +9 -0
- mannf/testing/models.py +9 -0
- mannf/weights/__init__.py +7 -0
- mannf/weights/registry.py +9 -0
- mannf/weights/store.py +9 -0
- nat_engine-1.dist-info/METADATA +555 -0
- nat_engine-1.dist-info/RECORD +299 -0
- nat_engine-1.dist-info/WHEEL +5 -0
- nat_engine-1.dist-info/entry_points.txt +4 -0
- nat_engine-1.dist-info/licenses/LICENSE +651 -0
- nat_engine-1.dist-info/licenses/NOTICE +178 -0
- nat_engine-1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
# Commercial licensing available — see COMMERCIAL_LICENSE.md.
|
|
5
|
+
|
|
6
|
+
"""Analyzer Agent – processes results, updates beliefs, detects anomalies.
|
|
7
|
+
|
|
8
|
+
The AnalyzerAgent is the "learning hub" of the NAT framework. It receives
|
|
9
|
+
test results from ExecutorAgents, runs its Oracle network for anomaly
|
|
10
|
+
detection, updates its BDI beliefs accordingly, and broadcasts BELIEF_UPDATE
|
|
11
|
+
messages to keep other agents informed (Thesis §4.2, §3.2).
|
|
12
|
+
|
|
13
|
+
Oracle Network:
|
|
14
|
+
An autoencoder-style anomaly detector trained on *passing* execution traces.
|
|
15
|
+
High reconstruction error on a new trace signals an anomaly.
|
|
16
|
+
|
|
17
|
+
Coverage tracking:
|
|
18
|
+
The analyzer maintains per-service execution counts and detects coverage
|
|
19
|
+
plateaus, triggering desire-weight adjustments to encourage exploration.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from collections import defaultdict, deque
|
|
26
|
+
from typing import Any, Deque, Dict, List, Optional, Set
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
from mannf.core.agents.bdi_agent import BDIAgent
|
|
31
|
+
from mannf.core.messaging.bus import MessageBus
|
|
32
|
+
from mannf.core.messaging.messages import Message, MessageType
|
|
33
|
+
from mannf.core.testing.models import TestResult
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
_ORACLE_IN = 6 # trace feature dimensions (matches BDIAgent oracle_net)
|
|
38
|
+
_HISTORY = 50 # sliding window for anomaly baseline
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AnalyzerAgent(BDIAgent):
|
|
42
|
+
"""Processes test execution data, maintains beliefs, detects anomalies."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
agent_id: str,
|
|
47
|
+
bus: MessageBus,
|
|
48
|
+
service_names: List[str],
|
|
49
|
+
broadcast_interval: int = 5,
|
|
50
|
+
) -> None:
|
|
51
|
+
super().__init__(agent_id, bus, service_names)
|
|
52
|
+
self._broadcast_interval = broadcast_interval
|
|
53
|
+
|
|
54
|
+
# Anomaly detection state
|
|
55
|
+
self._trace_history: Deque[np.ndarray] = deque(maxlen=_HISTORY)
|
|
56
|
+
self._anomaly_threshold: float = 1.0
|
|
57
|
+
|
|
58
|
+
# Coverage and statistics
|
|
59
|
+
self._service_counts: Dict[str, int] = defaultdict(int)
|
|
60
|
+
self._service_failures: Dict[str, int] = defaultdict(int)
|
|
61
|
+
self._total_results = 0
|
|
62
|
+
self._results_since_broadcast = 0
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def subscribed_types(self) -> Set[MessageType]:
|
|
66
|
+
return {
|
|
67
|
+
MessageType.TEST_RESULT,
|
|
68
|
+
MessageType.CONTRACT_RESULT,
|
|
69
|
+
MessageType.BELIEF_UPDATE,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ------------------------------------------------------------------
|
|
73
|
+
# Message handling
|
|
74
|
+
# ------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
async def _handle_message(self, message: Message) -> None:
|
|
77
|
+
if message.type in (MessageType.TEST_RESULT, MessageType.CONTRACT_RESULT):
|
|
78
|
+
payload = message.payload
|
|
79
|
+
result: Optional[TestResult] = (
|
|
80
|
+
payload.get("result") if isinstance(payload, dict) else
|
|
81
|
+
payload if isinstance(payload, TestResult) else None
|
|
82
|
+
)
|
|
83
|
+
if result:
|
|
84
|
+
await self._process_result(result)
|
|
85
|
+
|
|
86
|
+
elif message.type == MessageType.BELIEF_UPDATE:
|
|
87
|
+
self._absorb_peer_beliefs(
|
|
88
|
+
message.sender_id,
|
|
89
|
+
message.payload.get("beliefs", {}),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
# Result processing
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
async def _process_result(self, result: TestResult) -> None:
|
|
97
|
+
"""Update beliefs and detect anomalies from a test result."""
|
|
98
|
+
# Determine target service from result metadata
|
|
99
|
+
target = self._infer_target(result)
|
|
100
|
+
|
|
101
|
+
# Update service statistics
|
|
102
|
+
self._total_results += 1
|
|
103
|
+
if target:
|
|
104
|
+
self._service_counts[target] += 1
|
|
105
|
+
if not result.passed or result.error:
|
|
106
|
+
self._service_failures[target] += 1
|
|
107
|
+
|
|
108
|
+
# Update fault-likelihood belief (Thesis §3.3 bounded revision)
|
|
109
|
+
evidence = 0.0 if (result.passed and not result.error) else 1.0
|
|
110
|
+
old_belief = self.beliefs.fault_likelihood.get(target, 0.5)
|
|
111
|
+
self._update_belief_from_result(target, evidence)
|
|
112
|
+
new_belief = self.beliefs.fault_likelihood.get(target, 0.5)
|
|
113
|
+
|
|
114
|
+
# Broadcast belief update telemetry (fail-safe)
|
|
115
|
+
try:
|
|
116
|
+
from mannf.dashboard import telemetry as _tel
|
|
117
|
+
await _tel.broadcast_belief_update(
|
|
118
|
+
agent_id=self.agent_id,
|
|
119
|
+
endpoint=target,
|
|
120
|
+
old_value=old_belief,
|
|
121
|
+
new_value=new_belief,
|
|
122
|
+
evidence=evidence,
|
|
123
|
+
)
|
|
124
|
+
except Exception: # noqa: BLE001
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Anomaly detection via Oracle network
|
|
128
|
+
trace_vec = self._result_to_trace(result)
|
|
129
|
+
self._trace_history.append(trace_vec)
|
|
130
|
+
|
|
131
|
+
if result.passed and not result.error:
|
|
132
|
+
# Train Oracle on normal traces
|
|
133
|
+
self.oracle_net.train_step(
|
|
134
|
+
trace_vec,
|
|
135
|
+
np.array([0.0]) # label = not anomalous
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if len(self._trace_history) >= 10:
|
|
139
|
+
await self._check_anomaly(result, trace_vec)
|
|
140
|
+
|
|
141
|
+
# Periodically broadcast updated beliefs
|
|
142
|
+
self._results_since_broadcast += 1
|
|
143
|
+
if self._results_since_broadcast >= self._broadcast_interval:
|
|
144
|
+
self._results_since_broadcast = 0
|
|
145
|
+
await self._broadcast_beliefs()
|
|
146
|
+
|
|
147
|
+
def _infer_target(self, result: TestResult) -> Optional[str]:
|
|
148
|
+
"""Best-effort extraction of target service from a TestResult."""
|
|
149
|
+
output = result.actual_output
|
|
150
|
+
if isinstance(output, dict):
|
|
151
|
+
return output.get("service")
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def _result_to_trace(self, result: TestResult) -> np.ndarray:
|
|
155
|
+
"""Encode a TestResult into a 6-D execution trace vector."""
|
|
156
|
+
vec = np.zeros(_ORACLE_IN)
|
|
157
|
+
vec[0] = 1.0 if result.passed else 0.0
|
|
158
|
+
vec[1] = 1.0 if result.error else 0.0
|
|
159
|
+
vec[2] = min(result.execution_time_ms / 1000.0, 5.0)
|
|
160
|
+
total = self._total_results or 1
|
|
161
|
+
all_failures = sum(self._service_failures.values())
|
|
162
|
+
vec[3] = all_failures / total
|
|
163
|
+
vec[4] = min(total / 500.0, 1.0)
|
|
164
|
+
vec[5] = self.beliefs.entropy()
|
|
165
|
+
return vec
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
# Anomaly detection
|
|
169
|
+
# ------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
async def _check_anomaly(self, result: TestResult, vec: np.ndarray) -> None:
|
|
172
|
+
"""Run Oracle network; publish ANOMALY_DETECTED if threshold exceeded."""
|
|
173
|
+
anomaly_score = float(self.oracle_net.predict(vec)[0])
|
|
174
|
+
|
|
175
|
+
# Update dynamic threshold as 95th percentile of recent scores
|
|
176
|
+
recent_scores = [
|
|
177
|
+
float(self.oracle_net.predict(v)[0])
|
|
178
|
+
for v in list(self._trace_history)[-20:]
|
|
179
|
+
]
|
|
180
|
+
if recent_scores:
|
|
181
|
+
self._anomaly_threshold = float(np.percentile(recent_scores, 95)) + 0.1
|
|
182
|
+
|
|
183
|
+
if anomaly_score > self._anomaly_threshold and anomaly_score > 0.2:
|
|
184
|
+
logger.info(
|
|
185
|
+
"AnalyzerAgent %s: anomaly detected (score=%.4f > threshold=%.4f)",
|
|
186
|
+
self.agent_id, anomaly_score, self._anomaly_threshold,
|
|
187
|
+
)
|
|
188
|
+
await self._publish(
|
|
189
|
+
Message(
|
|
190
|
+
MessageType.ANOMALY_DETECTED,
|
|
191
|
+
sender_id=self.agent_id,
|
|
192
|
+
payload={
|
|
193
|
+
"test_case_id": result.test_case_id,
|
|
194
|
+
"anomaly_score": anomaly_score,
|
|
195
|
+
"threshold": self._anomaly_threshold,
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
# Diagnostics
|
|
202
|
+
# ------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def total_results(self) -> int:
|
|
206
|
+
return self._total_results
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def service_failure_counts(self) -> Dict[str, int]:
|
|
210
|
+
return dict(self._service_failures)
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def service_execution_counts(self) -> Dict[str, int]:
|
|
214
|
+
return dict(self._service_counts)
|
|
215
|
+
|
|
216
|
+
def metrics(self) -> Dict[str, Any]:
|
|
217
|
+
total = max(self._total_results, 1)
|
|
218
|
+
return {
|
|
219
|
+
"agent_id": self.agent_id,
|
|
220
|
+
"total_results": self._total_results,
|
|
221
|
+
"failure_rate": sum(self._service_failures.values()) / total,
|
|
222
|
+
"belief_entropy": round(self.beliefs.entropy(), 4),
|
|
223
|
+
"anomaly_threshold": round(self._anomaly_threshold, 6),
|
|
224
|
+
}
|