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.
Files changed (299) hide show
  1. mannf/__init__.py +33 -0
  2. mannf/__main__.py +10 -0
  3. mannf/_version.py +8 -0
  4. mannf/agents/__init__.py +7 -0
  5. mannf/agents/analyzer_agent.py +9 -0
  6. mannf/agents/base.py +9 -0
  7. mannf/agents/bdi_agent.py +9 -0
  8. mannf/agents/belief_state.py +9 -0
  9. mannf/agents/coordinator_agent.py +9 -0
  10. mannf/agents/executor_agent.py +9 -0
  11. mannf/agents/monitor_agent.py +9 -0
  12. mannf/agents/oracle_agent.py +9 -0
  13. mannf/agents/planner_agent.py +9 -0
  14. mannf/agents/test_agent.py +9 -0
  15. mannf/anomaly/__init__.py +7 -0
  16. mannf/anomaly/enhanced_detector.py +9 -0
  17. mannf/cli.py +9 -0
  18. mannf/core/__init__.py +26 -0
  19. mannf/core/agents/__init__.py +52 -0
  20. mannf/core/agents/accessibility_scanner_agent.py +245 -0
  21. mannf/core/agents/analyzer_agent.py +224 -0
  22. mannf/core/agents/autonomous_loop_agent.py +1086 -0
  23. mannf/core/agents/autonomous_loop_models.py +62 -0
  24. mannf/core/agents/autonomous_run_differ.py +427 -0
  25. mannf/core/agents/base.py +128 -0
  26. mannf/core/agents/bdi_agent.py +330 -0
  27. mannf/core/agents/belief_state.py +202 -0
  28. mannf/core/agents/browser_coordinator_agent.py +224 -0
  29. mannf/core/agents/browser_executor_agent.py +410 -0
  30. mannf/core/agents/coordinator_agent.py +262 -0
  31. mannf/core/agents/executor_agent.py +222 -0
  32. mannf/core/agents/monitor_agent.py +188 -0
  33. mannf/core/agents/oracle_agent.py +150 -0
  34. mannf/core/agents/performance_testing_agent.py +279 -0
  35. mannf/core/agents/planner_agent.py +128 -0
  36. mannf/core/agents/test_agent.py +249 -0
  37. mannf/core/agents/visual_regression_agent.py +311 -0
  38. mannf/core/agents/web_crawler_agent.py +510 -0
  39. mannf/core/agents/worker_pool.py +366 -0
  40. mannf/core/anomaly/__init__.py +14 -0
  41. mannf/core/anomaly/enhanced_detector.py +541 -0
  42. mannf/core/browser/__init__.py +63 -0
  43. mannf/core/browser/accessibility_scanner.py +424 -0
  44. mannf/core/browser/discovery_model.py +178 -0
  45. mannf/core/browser/dom_snapshot.py +349 -0
  46. mannf/core/browser/ingestor_bridge.py +371 -0
  47. mannf/core/browser/performance_metrics.py +217 -0
  48. mannf/core/browser/reflection_analyzer.py +442 -0
  49. mannf/core/browser/scenario_generator.py +1100 -0
  50. mannf/core/browser/security_scenario_generator.py +695 -0
  51. mannf/core/browser/visual_comparer.py +159 -0
  52. mannf/core/diagnostics/__init__.py +28 -0
  53. mannf/core/diagnostics/failure_clusterer.py +211 -0
  54. mannf/core/diagnostics/flake_detector.py +233 -0
  55. mannf/core/diagnostics/root_cause_analyzer.py +273 -0
  56. mannf/core/distributed/__init__.py +16 -0
  57. mannf/core/distributed/endpoint.py +139 -0
  58. mannf/core/distributed/system_under_test.py +207 -0
  59. mannf/core/functional_orchestrator.py +428 -0
  60. mannf/core/messaging/__init__.py +11 -0
  61. mannf/core/messaging/bus.py +113 -0
  62. mannf/core/messaging/messages.py +89 -0
  63. mannf/core/nat_orchestrator.py +342 -0
  64. mannf/core/neural/__init__.py +183 -0
  65. mannf/core/orchestrator.py +272 -0
  66. mannf/core/prioritization/__init__.py +17 -0
  67. mannf/core/prioritization/adaptive_controller.py +509 -0
  68. mannf/core/prioritization/belief_prioritizer.py +231 -0
  69. mannf/core/prioritization/risk_scorer.py +430 -0
  70. mannf/core/reporting/__init__.py +12 -0
  71. mannf/core/reporting/unified_report.py +664 -0
  72. mannf/core/testing/__init__.py +17 -0
  73. mannf/core/testing/adaptive_controller.py +149 -0
  74. mannf/core/testing/models.py +179 -0
  75. mannf/core/validation/__init__.py +10 -0
  76. mannf/core/validation/self_validation_runner.py +180 -0
  77. mannf/dashboard/__init__.py +7 -0
  78. mannf/dashboard/app.py +9 -0
  79. mannf/dashboard/models.py +9 -0
  80. mannf/dashboard/static/index.html +2538 -0
  81. mannf/dashboard/telemetry.py +9 -0
  82. mannf/distributed/__init__.py +7 -0
  83. mannf/distributed/endpoint.py +9 -0
  84. mannf/distributed/system_under_test.py +9 -0
  85. mannf/healing/__init__.py +7 -0
  86. mannf/healing/graphql_schema_diff.py +9 -0
  87. mannf/healing/healer.py +9 -0
  88. mannf/healing/models.py +9 -0
  89. mannf/healing/schema_diff.py +9 -0
  90. mannf/integrations/__init__.py +7 -0
  91. mannf/integrations/auth.py +9 -0
  92. mannf/integrations/graphql_parser.py +9 -0
  93. mannf/integrations/graphql_sut.py +9 -0
  94. mannf/integrations/http_sut.py +9 -0
  95. mannf/integrations/openapi_parser.py +9 -0
  96. mannf/integrations/postman_parser.py +9 -0
  97. mannf/llm/__init__.py +7 -0
  98. mannf/llm/anthropic_provider.py +9 -0
  99. mannf/llm/base.py +9 -0
  100. mannf/llm/config.py +9 -0
  101. mannf/llm/factory.py +9 -0
  102. mannf/llm/openai_provider.py +9 -0
  103. mannf/llm/prompts.py +9 -0
  104. mannf/messaging/__init__.py +7 -0
  105. mannf/messaging/bus.py +9 -0
  106. mannf/messaging/messages.py +9 -0
  107. mannf/nat_orchestrator.py +9 -0
  108. mannf/neural/__init__.py +7 -0
  109. mannf/orchestrator.py +9 -0
  110. mannf/prioritization/__init__.py +7 -0
  111. mannf/prioritization/adaptive_controller.py +9 -0
  112. mannf/prioritization/belief_prioritizer.py +9 -0
  113. mannf/prioritization/risk_scorer.py +9 -0
  114. mannf/product/__init__.py +29 -0
  115. mannf/product/admin/__init__.py +3 -0
  116. mannf/product/admin/routes.py +514 -0
  117. mannf/product/auth/__init__.py +5 -0
  118. mannf/product/auth/saml.py +212 -0
  119. mannf/product/billing/__init__.py +5 -0
  120. mannf/product/billing/audit.py +160 -0
  121. mannf/product/billing/feature_gates.py +180 -0
  122. mannf/product/billing/metering.py +179 -0
  123. mannf/product/billing/notifications.py +181 -0
  124. mannf/product/billing/plans.py +133 -0
  125. mannf/product/billing/rate_limits.py +35 -0
  126. mannf/product/billing/stripe_billing.py +906 -0
  127. mannf/product/billing/tenant_auth.py +233 -0
  128. mannf/product/billing/tenant_manager.py +873 -0
  129. mannf/product/cli.py +3900 -0
  130. mannf/product/cli_admin.py +408 -0
  131. mannf/product/dashboard/__init__.py +61 -0
  132. mannf/product/dashboard/app.py +3567 -0
  133. mannf/product/dashboard/models.py +460 -0
  134. mannf/product/dashboard/static/index.html +6347 -0
  135. mannf/product/dashboard/static/manifest.json +25 -0
  136. mannf/product/dashboard/static/pwa-icon-192.png +0 -0
  137. mannf/product/dashboard/static/pwa-icon-512.png +0 -0
  138. mannf/product/dashboard/static/sw.js +64 -0
  139. mannf/product/dashboard/telemetry.py +547 -0
  140. mannf/product/database.py +145 -0
  141. mannf/product/demo.py +844 -0
  142. mannf/product/doctor.py +509 -0
  143. mannf/product/exporters/__init__.py +65 -0
  144. mannf/product/exporters/azuredevops_exporter.py +257 -0
  145. mannf/product/exporters/base.py +307 -0
  146. mannf/product/exporters/bugzilla_exporter.py +200 -0
  147. mannf/product/exporters/dedup.py +275 -0
  148. mannf/product/exporters/finding_adapter.py +216 -0
  149. mannf/product/exporters/github_exporter.py +197 -0
  150. mannf/product/exporters/gitlab_exporter.py +215 -0
  151. mannf/product/exporters/jira_exporter.py +180 -0
  152. mannf/product/exporters/linear_exporter.py +195 -0
  153. mannf/product/exporters/loader.py +233 -0
  154. mannf/product/exporters/pagerduty_exporter.py +363 -0
  155. mannf/product/exporters/sentry_exporter.py +322 -0
  156. mannf/product/exporters/servicenow_exporter.py +240 -0
  157. mannf/product/exporters/shortcut_exporter.py +231 -0
  158. mannf/product/exporters/webhook_exporter.py +383 -0
  159. mannf/product/formatters/__init__.py +18 -0
  160. mannf/product/formatters/allure_formatter.py +161 -0
  161. mannf/product/formatters/ctrf_formatter.py +149 -0
  162. mannf/product/healing/__init__.py +30 -0
  163. mannf/product/healing/graphql_schema_diff.py +152 -0
  164. mannf/product/healing/healer.py +141 -0
  165. mannf/product/healing/models.py +175 -0
  166. mannf/product/healing/schema_diff.py +251 -0
  167. mannf/product/ingestors/__init__.py +77 -0
  168. mannf/product/ingestors/base.py +256 -0
  169. mannf/product/ingestors/bgstm_ingestor.py +764 -0
  170. mannf/product/ingestors/curl_ingestor.py +1019 -0
  171. mannf/product/ingestors/cypress_ingestor.py +487 -0
  172. mannf/product/ingestors/gherkin_ingestor.py +967 -0
  173. mannf/product/ingestors/graphql_ingestor.py +845 -0
  174. mannf/product/ingestors/grpc_ingestor.py +591 -0
  175. mannf/product/ingestors/har_ingestor.py +976 -0
  176. mannf/product/ingestors/loader.py +284 -0
  177. mannf/product/ingestors/models.py +146 -0
  178. mannf/product/ingestors/openapi_ingestor.py +606 -0
  179. mannf/product/ingestors/playwright_ingestor.py +449 -0
  180. mannf/product/ingestors/postman_ingestor.py +631 -0
  181. mannf/product/ingestors/traffic_ingestor.py +679 -0
  182. mannf/product/ingestors/websocket_ingestor.py +526 -0
  183. mannf/product/integrations/__init__.py +21 -0
  184. mannf/product/integrations/auth.py +190 -0
  185. mannf/product/integrations/graphql_parser.py +436 -0
  186. mannf/product/integrations/graphql_sut.py +247 -0
  187. mannf/product/integrations/grpc_sut.py +469 -0
  188. mannf/product/integrations/http_sut.py +237 -0
  189. mannf/product/integrations/kafka_adapter.py +342 -0
  190. mannf/product/integrations/openapi_parser.py +513 -0
  191. mannf/product/integrations/postman_parser.py +467 -0
  192. mannf/product/integrations/webhook_receiver.py +344 -0
  193. mannf/product/integrations/websocket_sut.py +434 -0
  194. mannf/product/llm/__init__.py +25 -0
  195. mannf/product/llm/anthropic_provider.py +94 -0
  196. mannf/product/llm/base.py +267 -0
  197. mannf/product/llm/config.py +48 -0
  198. mannf/product/llm/factory.py +42 -0
  199. mannf/product/llm/openai_provider.py +93 -0
  200. mannf/product/llm/prompts.py +403 -0
  201. mannf/product/llm/root_cause_service.py +311 -0
  202. mannf/product/llm/test_plan_models.py +78 -0
  203. mannf/product/metrics.py +149 -0
  204. mannf/product/middleware/__init__.py +3 -0
  205. mannf/product/middleware/audit_middleware.py +112 -0
  206. mannf/product/middleware/tenant_isolation.py +114 -0
  207. mannf/product/models.py +347 -0
  208. mannf/product/notifications/__init__.py +24 -0
  209. mannf/product/notifications/dispatcher.py +411 -0
  210. mannf/product/onboarding.py +190 -0
  211. mannf/product/orchestration/__init__.py +39 -0
  212. mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
  213. mannf/product/orchestration/pipeline.py +401 -0
  214. mannf/product/orchestrator.py +987 -0
  215. mannf/product/orchestrator_models.py +269 -0
  216. mannf/product/regression/__init__.py +36 -0
  217. mannf/product/regression/differ.py +172 -0
  218. mannf/product/regression/masking.py +100 -0
  219. mannf/product/regression/models.py +232 -0
  220. mannf/product/regression/recorder.py +124 -0
  221. mannf/product/regression/replayer.py +168 -0
  222. mannf/product/reports/__init__.py +10 -0
  223. mannf/product/reports/pdf.py +132 -0
  224. mannf/product/scheduling/__init__.py +57 -0
  225. mannf/product/scheduling/cron_utils.py +251 -0
  226. mannf/product/scheduling/engine.py +473 -0
  227. mannf/product/scheduling/models.py +86 -0
  228. mannf/product/scheduling/queue.py +894 -0
  229. mannf/product/scheduling/store.py +235 -0
  230. mannf/product/security/__init__.py +21 -0
  231. mannf/product/security/belief_guided.py +143 -0
  232. mannf/product/security/checks/__init__.py +55 -0
  233. mannf/product/security/checks/base.py +69 -0
  234. mannf/product/security/checks/bfla.py +77 -0
  235. mannf/product/security/checks/bola.py +77 -0
  236. mannf/product/security/checks/bopla.py +80 -0
  237. mannf/product/security/checks/broken_auth.py +86 -0
  238. mannf/product/security/checks/graphql_security.py +299 -0
  239. mannf/product/security/checks/inventory.py +70 -0
  240. mannf/product/security/checks/misconfig.py +158 -0
  241. mannf/product/security/checks/resource_consumption.py +70 -0
  242. mannf/product/security/checks/sensitive_flows.py +80 -0
  243. mannf/product/security/checks/ssrf.py +101 -0
  244. mannf/product/security/checks/unsafe_consumption.py +120 -0
  245. mannf/product/security/models.py +92 -0
  246. mannf/product/security/plugin_loader.py +182 -0
  247. mannf/product/security/reporter.py +92 -0
  248. mannf/product/security/scanner.py +183 -0
  249. mannf/product/server.py +6220 -0
  250. mannf/product/setup_wizard.py +873 -0
  251. mannf/product/status.py +404 -0
  252. mannf/product/storage/__init__.py +10 -0
  253. mannf/product/storage/artifact_store.py +343 -0
  254. mannf/product/telemetry.py +300 -0
  255. mannf/product/uninstall.py +169 -0
  256. mannf/product/upgrade.py +139 -0
  257. mannf/product/weights/__init__.py +13 -0
  258. mannf/product/weights/blob_store.py +299 -0
  259. mannf/product/weights/factory.py +42 -0
  260. mannf/product/weights/registry.py +159 -0
  261. mannf/product/weights/store.py +210 -0
  262. mannf/regression/__init__.py +7 -0
  263. mannf/regression/differ.py +9 -0
  264. mannf/regression/masking.py +9 -0
  265. mannf/regression/models.py +9 -0
  266. mannf/regression/recorder.py +9 -0
  267. mannf/regression/replayer.py +9 -0
  268. mannf/security/__init__.py +7 -0
  269. mannf/security/belief_guided.py +9 -0
  270. mannf/security/checks/__init__.py +7 -0
  271. mannf/security/checks/base.py +9 -0
  272. mannf/security/checks/bfla.py +9 -0
  273. mannf/security/checks/bola.py +9 -0
  274. mannf/security/checks/bopla.py +9 -0
  275. mannf/security/checks/broken_auth.py +9 -0
  276. mannf/security/checks/graphql_security.py +9 -0
  277. mannf/security/checks/inventory.py +9 -0
  278. mannf/security/checks/misconfig.py +9 -0
  279. mannf/security/checks/resource_consumption.py +9 -0
  280. mannf/security/checks/sensitive_flows.py +9 -0
  281. mannf/security/checks/ssrf.py +9 -0
  282. mannf/security/checks/unsafe_consumption.py +9 -0
  283. mannf/security/models.py +9 -0
  284. mannf/security/reporter.py +9 -0
  285. mannf/security/scanner.py +9 -0
  286. mannf/server.py +9 -0
  287. mannf/testing/__init__.py +7 -0
  288. mannf/testing/adaptive_controller.py +9 -0
  289. mannf/testing/models.py +9 -0
  290. mannf/weights/__init__.py +7 -0
  291. mannf/weights/registry.py +9 -0
  292. mannf/weights/store.py +9 -0
  293. nat_engine-1.dist-info/METADATA +555 -0
  294. nat_engine-1.dist-info/RECORD +299 -0
  295. nat_engine-1.dist-info/WHEEL +5 -0
  296. nat_engine-1.dist-info/entry_points.txt +4 -0
  297. nat_engine-1.dist-info/licenses/LICENSE +651 -0
  298. nat_engine-1.dist-info/licenses/NOTICE +178 -0
  299. nat_engine-1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,149 @@
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
+ """Adaptive testing controller.
7
+
8
+ Maintains a neural-network-based policy that scores candidate test cases
9
+ and decides *which* test to run next (exploitation) vs *generating* a new
10
+ exploratory test (exploration).
11
+
12
+ The exploration–exploitation trade-off is managed with an ε-greedy strategy
13
+ where ε decays over time as the agents gain experience.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import math
20
+ from typing import List, Optional
21
+
22
+ import numpy as np
23
+
24
+ from mannf.core.neural import NeuralNetwork
25
+ from mannf.core.testing.models import TestCase, TestResult
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Feature dimensionality must match TestCase.feature_vector()
30
+ _FEATURE_DIM = 8
31
+
32
+
33
+ class AdaptiveController:
34
+ """Selects and prioritises test cases using a learned value function.
35
+
36
+ The controller maintains a *value network* V(s) that predicts the
37
+ expected reward (defect-finding potential) of a test case. After each
38
+ test execution, the network is updated with the observed reward via a
39
+ single-sample gradient step (online learning).
40
+
41
+ Parameters
42
+ ----------
43
+ initial_epsilon:
44
+ Initial exploration probability (0–1). 1 = fully random.
45
+ min_epsilon:
46
+ Minimum exploration probability after decay.
47
+ epsilon_decay:
48
+ Multiplicative decay applied after each test execution.
49
+ learning_rate:
50
+ Step size for the value-network update.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ initial_epsilon: float = 0.9,
56
+ min_epsilon: float = 0.05,
57
+ epsilon_decay: float = 0.995,
58
+ learning_rate: float = 0.005,
59
+ ) -> None:
60
+ self.epsilon = initial_epsilon
61
+ self.min_epsilon = min_epsilon
62
+ self.epsilon_decay = epsilon_decay
63
+
64
+ # Value network: maps a test-case feature vector to a scalar reward estimate
65
+ self.value_net = NeuralNetwork(
66
+ layer_sizes=[_FEATURE_DIM, 16, 8, 1],
67
+ hidden_activation="relu",
68
+ learning_rate=learning_rate,
69
+ )
70
+
71
+ self._step_count = 0
72
+ self._cumulative_reward = 0.0
73
+
74
+ # ------------------------------------------------------------------
75
+ # Selection
76
+ # ------------------------------------------------------------------
77
+
78
+ def select(self, candidates: List[TestCase]) -> Optional[TestCase]:
79
+ """Pick the next test case to execute.
80
+
81
+ With probability *ε* a random candidate is selected (exploration);
82
+ otherwise the candidate with the highest predicted reward is chosen
83
+ (exploitation).
84
+ """
85
+ if not candidates:
86
+ return None
87
+
88
+ if np.random.random() < self.epsilon:
89
+ choice = candidates[int(np.random.randint(len(candidates)))]
90
+ logger.debug("AdaptiveController: exploring → %s", choice.id[:8])
91
+ else:
92
+ scores = [
93
+ float(self.value_net.predict(c.feature_vector(_FEATURE_DIM))[0])
94
+ for c in candidates
95
+ ]
96
+ best_idx = int(np.argmax(scores))
97
+ choice = candidates[best_idx]
98
+ logger.debug(
99
+ "AdaptiveController: exploiting → %s (score=%.4f)",
100
+ choice.id[:8],
101
+ scores[best_idx],
102
+ )
103
+ return choice
104
+
105
+ # ------------------------------------------------------------------
106
+ # Learning
107
+ # ------------------------------------------------------------------
108
+
109
+ def update(self, test_case: TestCase, result: TestResult) -> float:
110
+ """Update the value network with the observed reward signal.
111
+
112
+ Returns the MSE training loss for the current step.
113
+ """
114
+ reward = result.reward_signal()
115
+ self._cumulative_reward += reward
116
+
117
+ x = test_case.feature_vector(_FEATURE_DIM)
118
+ target = np.array([reward])
119
+ loss = self.value_net.train_step(x, target)
120
+
121
+ # Decay exploration rate
122
+ self.epsilon = max(self.min_epsilon, self.epsilon * self.epsilon_decay)
123
+ self._step_count += 1
124
+
125
+ logger.debug(
126
+ "AdaptiveController update: reward=%.3f loss=%.6f ε=%.4f",
127
+ reward,
128
+ loss,
129
+ self.epsilon,
130
+ )
131
+ return loss
132
+
133
+ # ------------------------------------------------------------------
134
+ # Diagnostics
135
+ # ------------------------------------------------------------------
136
+
137
+ @property
138
+ def step_count(self) -> int:
139
+ return self._step_count
140
+
141
+ @property
142
+ def average_reward(self) -> float:
143
+ if self._step_count == 0:
144
+ return 0.0
145
+ return self._cumulative_reward / self._step_count
146
+
147
+ def score(self, test_case: TestCase) -> float:
148
+ """Return the current predicted value for a test case."""
149
+ return float(self.value_net.predict(test_case.feature_vector(_FEATURE_DIM))[0])
@@ -0,0 +1,179 @@
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
+ """Core data models for the adaptive testing layer."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import uuid
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum, auto
13
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
14
+
15
+ if TYPE_CHECKING:
16
+ import numpy as np
17
+
18
+
19
+ class TestStatus(Enum):
20
+ PENDING = auto()
21
+ RUNNING = auto()
22
+ PASSED = auto()
23
+ FAILED = auto()
24
+ ERROR = auto()
25
+ SKIPPED = auto()
26
+
27
+
28
+ @dataclass
29
+ class TestCase:
30
+ """Represents a single test to be executed against a distributed endpoint.
31
+
32
+ Parameters
33
+ ----------
34
+ target:
35
+ Logical name of the service/endpoint being tested.
36
+ inputs:
37
+ Dictionary of input parameters to send.
38
+ expected_behavior:
39
+ Human-readable description of the expected outcome.
40
+ priority:
41
+ Float in [0, 1]; higher values mean the test should run sooner.
42
+ metadata:
43
+ Arbitrary key-value pairs (e.g. tags, author, generation strategy).
44
+ """
45
+
46
+ target: str
47
+ inputs: Dict[str, Any]
48
+ expected_behavior: str
49
+ priority: float = 0.5
50
+ metadata: Dict[str, Any] = field(default_factory=dict)
51
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
52
+ status: TestStatus = TestStatus.PENDING
53
+
54
+ def feature_vector(self, feature_dim: int = 8) -> "np.ndarray":
55
+ """Return a fixed-length numeric feature vector used as NN input.
56
+
57
+ The vector encodes: priority, input complexity, id hash projection.
58
+ """
59
+ import numpy as np
60
+
61
+ vec = np.zeros(feature_dim)
62
+ vec[0] = self.priority
63
+ vec[1] = len(self.inputs) / 20.0 # normalised input arity
64
+ vec[2] = len(self.target) / 50.0 # normalised target length
65
+ # Hash the id into the remaining dimensions for uniqueness
66
+ h = hash(self.id) & 0xFFFFFF
67
+ for i in range(3, feature_dim):
68
+ vec[i] = ((h >> (i * 3)) & 0x7) / 7.0
69
+ return vec
70
+
71
+
72
+ @dataclass
73
+ class TestResult:
74
+ """The outcome of executing a :class:`TestCase`.
75
+
76
+ Parameters
77
+ ----------
78
+ test_case_id:
79
+ The id of the test case that was executed.
80
+ passed:
81
+ Whether the test produced the expected behaviour.
82
+ actual_output:
83
+ Raw output returned by the system under test.
84
+ execution_time_ms:
85
+ Wall-clock time taken to execute the test.
86
+ error:
87
+ Exception message if an error occurred (``None`` otherwise).
88
+ agent_id:
89
+ ID of the agent that executed/validated the test.
90
+ """
91
+
92
+ test_case_id: str
93
+ passed: bool
94
+ actual_output: Any = None
95
+ execution_time_ms: float = 0.0
96
+ error: Optional[str] = None
97
+ agent_id: Optional[str] = None
98
+
99
+ @property
100
+ def status(self) -> TestStatus:
101
+ if self.error:
102
+ return TestStatus.ERROR
103
+ return TestStatus.PASSED if self.passed else TestStatus.FAILED
104
+
105
+ def reward_signal(self) -> float:
106
+ """Scalar reward used to update agent neural networks.
107
+
108
+ * +1.0 – test failed (found a real defect; useful information)
109
+ * +0.2 – test errored (interesting, but possibly noisy)
110
+ * -0.1 – test passed (confirms existing behaviour; less informative)
111
+
112
+ The asymmetric reward encourages agents to generate test cases that
113
+ find failures (the primary goal of adaptive testing).
114
+ """
115
+ if self.error:
116
+ return 0.2
117
+ return 1.0 if not self.passed else -0.1
118
+
119
+
120
+ @dataclass
121
+ class TestSuite:
122
+ """An ordered collection of :class:`TestCase` objects with run statistics."""
123
+
124
+ name: str
125
+ cases: List[TestCase] = field(default_factory=list)
126
+ results: List[TestResult] = field(default_factory=list)
127
+
128
+ # -- mutators ---------------------------------------------------------
129
+
130
+ def add_case(self, case: TestCase) -> None:
131
+ self.cases.append(case)
132
+
133
+ def record_result(self, result: TestResult) -> None:
134
+ self.results.append(result)
135
+
136
+ # -- statistics -------------------------------------------------------
137
+
138
+ @property
139
+ def total(self) -> int:
140
+ return len(self.results)
141
+
142
+ @property
143
+ def passed(self) -> int:
144
+ return sum(1 for r in self.results if r.passed and not r.error)
145
+
146
+ @property
147
+ def failed(self) -> int:
148
+ return sum(1 for r in self.results if not r.passed and not r.error)
149
+
150
+ @property
151
+ def errored(self) -> int:
152
+ return sum(1 for r in self.results if r.error)
153
+
154
+ @property
155
+ def pass_rate(self) -> float:
156
+ return self.passed / self.total if self.total > 0 else 0.0
157
+
158
+ @property
159
+ def defect_detection_rate(self) -> float:
160
+ """Fraction of executions that revealed a defect (failed or errored)."""
161
+ return (self.failed + self.errored) / self.total if self.total > 0 else 0.0
162
+
163
+ @property
164
+ def average_execution_time_ms(self) -> float:
165
+ if not self.results:
166
+ return 0.0
167
+ return sum(r.execution_time_ms for r in self.results) / len(self.results)
168
+
169
+ def summary(self) -> Dict[str, Any]:
170
+ return {
171
+ "name": self.name,
172
+ "total": self.total,
173
+ "passed": self.passed,
174
+ "failed": self.failed,
175
+ "errored": self.errored,
176
+ "pass_rate": round(self.pass_rate, 4),
177
+ "defect_detection_rate": round(self.defect_detection_rate, 4),
178
+ "avg_execution_time_ms": round(self.average_execution_time_ms, 2),
179
+ }
@@ -0,0 +1,10 @@
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
+ """Validation sub-package for self-validation and canary testing (Phase 7.4)."""
7
+
8
+ from mannf.core.validation.self_validation_runner import SelfValidationRunner
9
+
10
+ __all__ = ["SelfValidationRunner"]
@@ -0,0 +1,180 @@
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
+ """Self-validation canary runner (Phase 7.4).
7
+
8
+ :class:`SelfValidationRunner` runs a set of *known-good* and *known-bad*
9
+ canary endpoints against the predictor and compares predicted vs. actual
10
+ outcomes. The accuracy, precision, recall, and F1 metrics are returned in a
11
+ structured report so leadership and auditors can verify detection fidelity.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import math
18
+ from typing import Any, Dict, List, Optional, Sequence
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class SelfValidationRunner:
24
+ """Compare predicted fault likelihood against known-ground-truth outcomes.
25
+
26
+ Parameters
27
+ ----------
28
+ known_good:
29
+ Sequence of endpoint keys (e.g. ``"/api/health"``) that are expected
30
+ to *pass* (ground truth = non-faulty).
31
+ known_bad:
32
+ Sequence of endpoint keys expected to *fail* (ground truth = faulty).
33
+ threshold:
34
+ Predicted fault-likelihood above which an endpoint is classified as
35
+ *faulty*. Defaults to ``0.5``.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ known_good: Sequence[str],
41
+ known_bad: Sequence[str],
42
+ threshold: float = 0.5,
43
+ ) -> None:
44
+ if not 0.0 < threshold < 1.0:
45
+ raise ValueError(f"threshold must be in (0.0, 1.0), got {threshold!r}")
46
+ self.known_good: List[str] = list(known_good)
47
+ self.known_bad: List[str] = list(known_bad)
48
+ self.threshold = threshold
49
+
50
+ # ------------------------------------------------------------------
51
+ # Public API
52
+ # ------------------------------------------------------------------
53
+
54
+ def run(
55
+ self,
56
+ predicted: Dict[str, float],
57
+ ) -> Dict[str, Any]:
58
+ """Evaluate predicted fault likelihoods against ground-truth labels.
59
+
60
+ Parameters
61
+ ----------
62
+ predicted:
63
+ Mapping of ``{endpoint: fault_likelihood}`` (values in [0, 1]).
64
+ Endpoints absent from *predicted* are treated as ``0.0`` (assumed
65
+ safe).
66
+
67
+ Returns
68
+ -------
69
+ dict
70
+ Structured validation report containing:
71
+
72
+ ``accuracy``, ``precision``, ``recall``, ``f1`` — overall
73
+ classification metrics (float in [0, 1]).
74
+
75
+ ``tp``, ``fp``, ``tn``, ``fn`` — raw confusion-matrix counts.
76
+
77
+ ``per_endpoint`` — list of per-endpoint dicts with
78
+ ``endpoint``, ``ground_truth``, ``predicted_score``,
79
+ ``predicted_label``, ``correct``.
80
+
81
+ ``threshold`` — the decision threshold used.
82
+
83
+ ``total_canaries`` — total number of canary endpoints evaluated.
84
+ """
85
+ per_endpoint: List[Dict[str, Any]] = []
86
+ tp = fp = tn = fn = 0
87
+
88
+ all_canaries = [
89
+ (ep, True) for ep in self.known_bad
90
+ ] + [
91
+ (ep, False) for ep in self.known_good
92
+ ]
93
+
94
+ for ep, ground_truth_faulty in all_canaries:
95
+ score = float(predicted.get(ep, 0.0))
96
+ predicted_faulty = score >= self.threshold
97
+ correct = predicted_faulty == ground_truth_faulty
98
+
99
+ if ground_truth_faulty and predicted_faulty:
100
+ tp += 1
101
+ elif not ground_truth_faulty and predicted_faulty:
102
+ fp += 1
103
+ elif not ground_truth_faulty and not predicted_faulty:
104
+ tn += 1
105
+ else:
106
+ fn += 1
107
+
108
+ per_endpoint.append(
109
+ {
110
+ "endpoint": ep,
111
+ "ground_truth": "faulty" if ground_truth_faulty else "healthy",
112
+ "predicted_score": round(score, 4),
113
+ "predicted_label": "faulty" if predicted_faulty else "healthy",
114
+ "correct": correct,
115
+ }
116
+ )
117
+
118
+ total = tp + fp + tn + fn
119
+ accuracy = (tp + tn) / total if total > 0 else 0.0
120
+ precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
121
+ recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
122
+ f1 = (
123
+ 2 * precision * recall / (precision + recall)
124
+ if (precision + recall) > 0
125
+ else 0.0
126
+ )
127
+
128
+ logger.info(
129
+ "SelfValidationRunner: accuracy=%.3f precision=%.3f recall=%.3f f1=%.3f "
130
+ "(tp=%d fp=%d tn=%d fn=%d threshold=%.2f)",
131
+ accuracy, precision, recall, f1, tp, fp, tn, fn, self.threshold,
132
+ )
133
+
134
+ return {
135
+ "accuracy": round(accuracy, 4),
136
+ "precision": round(precision, 4),
137
+ "recall": round(recall, 4),
138
+ "f1": round(f1, 4),
139
+ "tp": tp,
140
+ "fp": fp,
141
+ "tn": tn,
142
+ "fn": fn,
143
+ "threshold": self.threshold,
144
+ "total_canaries": total,
145
+ "per_endpoint": per_endpoint,
146
+ }
147
+
148
+ # ------------------------------------------------------------------
149
+ # Convenience helper
150
+ # ------------------------------------------------------------------
151
+
152
+ @staticmethod
153
+ def from_risk_report(
154
+ risk_report: List[Dict[str, Any]],
155
+ known_good: Sequence[str],
156
+ known_bad: Sequence[str],
157
+ threshold: float = 0.5,
158
+ ) -> "SelfValidationRunner":
159
+ """Run self-validation directly from a :meth:`RiskScorer.get_risk_report` result.
160
+
161
+ Parameters
162
+ ----------
163
+ risk_report:
164
+ Output of ``RiskScorer.get_risk_report()``.
165
+ known_good:
166
+ Endpoint keys expected to be healthy.
167
+ known_bad:
168
+ Endpoint keys expected to be faulty.
169
+ threshold:
170
+ Decision threshold.
171
+
172
+ Returns
173
+ -------
174
+ SelfValidationRunner
175
+ A runner pre-configured with the supplied canary lists.
176
+ """
177
+ runner = SelfValidationRunner(known_good, known_bad, threshold)
178
+ predicted = {entry["endpoint"]: entry["risk_score"] for entry in risk_report}
179
+ runner._last_report = runner.run(predicted) # type: ignore[attr-defined]
180
+ return runner
@@ -0,0 +1,7 @@
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
+ # Backward-compatible shim — source of truth has moved to mannf.product.dashboard
7
+ from mannf.product.dashboard import * # noqa: F401, F403
mannf/dashboard/app.py ADDED
@@ -0,0 +1,9 @@
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
+ # Backward-compatible shim — source of truth has moved to mannf.product.dashboard.app
7
+ import sys
8
+ import mannf.product.dashboard.app as _real
9
+ sys.modules[__name__] = _real
@@ -0,0 +1,9 @@
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
+ # Backward-compatible shim — source of truth has moved to mannf.product.dashboard.models
7
+ import sys
8
+ import mannf.product.dashboard.models as _real
9
+ sys.modules[__name__] = _real