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,272 @@
|
|
|
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
|
+
"""Orchestrator – the central coordinator of the MANNF framework.
|
|
7
|
+
|
|
8
|
+
The :class:`Orchestrator` manages:
|
|
9
|
+
1. Startup / shutdown of all agents.
|
|
10
|
+
2. The test-execution loop: request test cases → execute → record results.
|
|
11
|
+
3. The adaptive feedback loop: update the :class:`AdaptiveController` after
|
|
12
|
+
each test and broadcast strategy updates to agents.
|
|
13
|
+
4. Progress logging and final report generation.
|
|
14
|
+
|
|
15
|
+
Usage::
|
|
16
|
+
|
|
17
|
+
from mannf import Orchestrator
|
|
18
|
+
from mannf.core.distributed import MockDistributedSystem
|
|
19
|
+
|
|
20
|
+
sut = MockDistributedSystem(seed=42)
|
|
21
|
+
orch = Orchestrator(sut, num_test_agents=3, max_steps=200)
|
|
22
|
+
await orch.run()
|
|
23
|
+
report = orch.report()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
import logging
|
|
30
|
+
import time
|
|
31
|
+
from typing import Any, Dict, List, Optional
|
|
32
|
+
|
|
33
|
+
from mannf.core.agents.monitor_agent import MonitorAgent
|
|
34
|
+
from mannf.core.agents.oracle_agent import OracleAgent
|
|
35
|
+
from mannf.core.agents.test_agent import TestAgent
|
|
36
|
+
from mannf.core.distributed.system_under_test import SystemUnderTest
|
|
37
|
+
from mannf.core.messaging.bus import MessageBus
|
|
38
|
+
from mannf.core.messaging.messages import Message, MessageType
|
|
39
|
+
from mannf.core.testing.adaptive_controller import AdaptiveController
|
|
40
|
+
from mannf.core.testing.models import TestCase, TestResult, TestSuite
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Orchestrator:
|
|
46
|
+
"""Coordinates all agents and drives the adaptive testing loop.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
sut:
|
|
51
|
+
The system under test (real or mock).
|
|
52
|
+
num_test_agents:
|
|
53
|
+
How many parallel :class:`TestAgent` instances to run.
|
|
54
|
+
max_steps:
|
|
55
|
+
Total number of test-execution steps before stopping.
|
|
56
|
+
suite_name:
|
|
57
|
+
Name used in the test suite and final report.
|
|
58
|
+
log_interval:
|
|
59
|
+
Log a progress summary every *log_interval* steps.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
sut: SystemUnderTest,
|
|
65
|
+
num_test_agents: int = 2,
|
|
66
|
+
max_steps: int = 100,
|
|
67
|
+
suite_name: str = "Adaptive Test Run",
|
|
68
|
+
log_interval: int = 10,
|
|
69
|
+
) -> None:
|
|
70
|
+
self.sut = sut
|
|
71
|
+
self.max_steps = max_steps
|
|
72
|
+
self.log_interval = log_interval
|
|
73
|
+
|
|
74
|
+
self._bus = MessageBus()
|
|
75
|
+
self._suite = TestSuite(name=suite_name)
|
|
76
|
+
self._controller = AdaptiveController()
|
|
77
|
+
|
|
78
|
+
service_names = sut.registry.all()
|
|
79
|
+
svc_names = [e.name for e in service_names]
|
|
80
|
+
|
|
81
|
+
# Create agents
|
|
82
|
+
self._test_agents: List[TestAgent] = [
|
|
83
|
+
TestAgent(f"test-agent-{i}", self._bus, svc_names)
|
|
84
|
+
for i in range(num_test_agents)
|
|
85
|
+
]
|
|
86
|
+
self._monitor = MonitorAgent("monitor-agent", self._bus)
|
|
87
|
+
self._oracle = OracleAgent("oracle-agent", self._bus)
|
|
88
|
+
|
|
89
|
+
self._step = 0
|
|
90
|
+
self._start_time: Optional[float] = None
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
# Main entry point
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
async def run(self) -> "TestSuite":
|
|
97
|
+
"""Execute the full adaptive testing loop.
|
|
98
|
+
|
|
99
|
+
Agents run as concurrent asyncio tasks so they process messages from
|
|
100
|
+
the bus while the orchestrator drives the test-execution loop.
|
|
101
|
+
|
|
102
|
+
Returns the completed :class:`TestSuite`.
|
|
103
|
+
"""
|
|
104
|
+
self._start_time = time.monotonic()
|
|
105
|
+
logger.info("Orchestrator: starting with max_steps=%d", self.max_steps)
|
|
106
|
+
|
|
107
|
+
all_agents = [self._monitor, self._oracle] + self._test_agents
|
|
108
|
+
|
|
109
|
+
# Launch each agent as an independent asyncio task (their run() loops
|
|
110
|
+
# process bus messages concurrently with the test-execution loop).
|
|
111
|
+
agent_tasks = [asyncio.create_task(agent.run()) for agent in all_agents]
|
|
112
|
+
|
|
113
|
+
# Give agents a moment to complete their start() subscriptions
|
|
114
|
+
await asyncio.sleep(0)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
await self._run_loop()
|
|
118
|
+
finally:
|
|
119
|
+
# Signal all agents to stop and wait for clean shutdown
|
|
120
|
+
for agent in all_agents:
|
|
121
|
+
agent._running = False
|
|
122
|
+
await asyncio.gather(*agent_tasks, return_exceptions=True)
|
|
123
|
+
|
|
124
|
+
elapsed = time.monotonic() - (self._start_time or 0)
|
|
125
|
+
logger.info(
|
|
126
|
+
"Orchestrator: finished %d steps in %.1f s", self._step, elapsed
|
|
127
|
+
)
|
|
128
|
+
return self._suite
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------------
|
|
131
|
+
# Test-execution loop
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
async def _run_loop(self) -> None:
|
|
135
|
+
while self._step < self.max_steps:
|
|
136
|
+
# 1. Pick a test agent (round-robin)
|
|
137
|
+
agent = self._test_agents[self._step % len(self._test_agents)]
|
|
138
|
+
|
|
139
|
+
# 2. Request a test case
|
|
140
|
+
test_case = await self._request_test_case(agent)
|
|
141
|
+
if test_case is None:
|
|
142
|
+
await asyncio.sleep(0.01)
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# 3. Apply adaptive selection from a small candidate pool
|
|
146
|
+
candidates = [test_case]
|
|
147
|
+
for _ in range(2):
|
|
148
|
+
extra = await self._request_test_case(
|
|
149
|
+
self._test_agents[(self._step + 1) % len(self._test_agents)]
|
|
150
|
+
)
|
|
151
|
+
if extra:
|
|
152
|
+
candidates.append(extra)
|
|
153
|
+
|
|
154
|
+
selected = self._controller.select(candidates) or test_case
|
|
155
|
+
|
|
156
|
+
# 4. Execute against the SUT
|
|
157
|
+
result = await self.sut.execute(selected)
|
|
158
|
+
result.agent_id = agent.agent_id
|
|
159
|
+
|
|
160
|
+
# 5. Record result
|
|
161
|
+
self._suite.add_case(selected)
|
|
162
|
+
self._suite.record_result(result)
|
|
163
|
+
|
|
164
|
+
# 6. Broadcast result to monitor & oracle (single unified message)
|
|
165
|
+
await self._bus.publish(
|
|
166
|
+
Message(
|
|
167
|
+
MessageType.TEST_RESULT,
|
|
168
|
+
"orchestrator",
|
|
169
|
+
{"result": result, "test_case": selected},
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# 7. Update the adaptive controller
|
|
174
|
+
self._controller.update(selected, result)
|
|
175
|
+
|
|
176
|
+
# 8. Send feedback to the test agents
|
|
177
|
+
feedback = self._build_feedback(selected, result)
|
|
178
|
+
await self._bus.publish(
|
|
179
|
+
Message(MessageType.FEEDBACK, "orchestrator", feedback)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
self._step += 1
|
|
183
|
+
|
|
184
|
+
if self._step % self.log_interval == 0:
|
|
185
|
+
self._log_progress()
|
|
186
|
+
|
|
187
|
+
# Yield control to allow agent background tasks to process messages
|
|
188
|
+
await asyncio.sleep(0)
|
|
189
|
+
|
|
190
|
+
# ------------------------------------------------------------------
|
|
191
|
+
# Helpers
|
|
192
|
+
# ------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
async def _request_test_case(self, agent: TestAgent) -> Optional[TestCase]:
|
|
195
|
+
"""Ask an agent to generate a test case synchronously (in-process)."""
|
|
196
|
+
context = {
|
|
197
|
+
"pass_rate": self._suite.pass_rate,
|
|
198
|
+
"defect_detection_rate": self._suite.defect_detection_rate,
|
|
199
|
+
"step_count": self._step,
|
|
200
|
+
}
|
|
201
|
+
req = Message(MessageType.TEST_REQUEST, "orchestrator", context)
|
|
202
|
+
# Call the agent's generation logic directly for synchronous behaviour
|
|
203
|
+
test_case = agent._generate_test_case(context=context)
|
|
204
|
+
return test_case
|
|
205
|
+
|
|
206
|
+
def _build_feedback(
|
|
207
|
+
self, test_case: TestCase, result: TestResult
|
|
208
|
+
) -> Dict[str, Any]:
|
|
209
|
+
"""Build a feedback payload for the test agents."""
|
|
210
|
+
reward = result.reward_signal()
|
|
211
|
+
features = test_case.feature_vector().tolist()
|
|
212
|
+
# Target: bias towards the service that produced the reward
|
|
213
|
+
target = [0.0] * max(len(self._test_agents), 1)
|
|
214
|
+
return {
|
|
215
|
+
"features": features,
|
|
216
|
+
"target": [reward],
|
|
217
|
+
"reward": reward,
|
|
218
|
+
"test_case_id": test_case.id,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
def _log_progress(self) -> None:
|
|
222
|
+
summary = self._suite.summary()
|
|
223
|
+
elapsed = time.monotonic() - (self._start_time or time.monotonic())
|
|
224
|
+
logger.info(
|
|
225
|
+
"Step %4d/%d | passed=%d failed=%d errored=%d "
|
|
226
|
+
"| defect_rate=%.1f%% | ε=%.3f | elapsed=%.1fs",
|
|
227
|
+
self._step,
|
|
228
|
+
self.max_steps,
|
|
229
|
+
summary["passed"],
|
|
230
|
+
summary["failed"],
|
|
231
|
+
summary["errored"],
|
|
232
|
+
summary["defect_detection_rate"] * 100,
|
|
233
|
+
self._controller.epsilon,
|
|
234
|
+
elapsed,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# ------------------------------------------------------------------
|
|
238
|
+
# Reporting
|
|
239
|
+
# ------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def report(self) -> Dict[str, Any]:
|
|
242
|
+
"""Return a structured summary of the completed test run."""
|
|
243
|
+
elapsed = (
|
|
244
|
+
time.monotonic() - self._start_time if self._start_time else 0.0
|
|
245
|
+
)
|
|
246
|
+
suite_summary = self._suite.summary()
|
|
247
|
+
monitor_metrics = self._monitor.metrics
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
"suite": suite_summary,
|
|
251
|
+
"monitor": monitor_metrics,
|
|
252
|
+
"oracle": {
|
|
253
|
+
"verdict_count": self._oracle.verdict_count,
|
|
254
|
+
"defect_count": self._oracle.defect_count,
|
|
255
|
+
"defect_rate": round(self._oracle.defect_rate, 4),
|
|
256
|
+
},
|
|
257
|
+
"adaptive_controller": {
|
|
258
|
+
"step_count": self._controller.step_count,
|
|
259
|
+
"epsilon": round(self._controller.epsilon, 4),
|
|
260
|
+
"average_reward": round(self._controller.average_reward, 4),
|
|
261
|
+
},
|
|
262
|
+
"agent_count": len(self._test_agents) + 2, # +monitor +oracle
|
|
263
|
+
"elapsed_seconds": round(elapsed, 2),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def suite(self) -> TestSuite:
|
|
268
|
+
return self._suite
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def controller(self) -> AdaptiveController:
|
|
272
|
+
return self._controller
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
"""Prioritization sub-package for belief-driven test ordering and risk scoring."""
|
|
7
|
+
|
|
8
|
+
from mannf.core.prioritization.adaptive_controller import AdaptiveController
|
|
9
|
+
from mannf.core.prioritization.belief_prioritizer import BeliefPrioritizer, PrioritizationStrategy
|
|
10
|
+
from mannf.core.prioritization.risk_scorer import RiskScorer
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BeliefPrioritizer",
|
|
14
|
+
"PrioritizationStrategy",
|
|
15
|
+
"RiskScorer",
|
|
16
|
+
"AdaptiveController",
|
|
17
|
+
]
|