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,404 @@
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
+ """Component health-check helpers for the NAT status endpoint.
7
+
8
+ Each public ``check_*`` coroutine returns a :class:`ComponentResult` that
9
+ describes the health of one subsystem. :func:`run_all_checks` aggregates
10
+ them into a list and :func:`aggregate_status` derives the overall status.
11
+
12
+ Statuses
13
+ --------
14
+ - ``healthy`` — the component is reachable and operating normally.
15
+ - ``degraded`` — the component is reachable but reporting a non-critical issue.
16
+ - ``unhealthy`` — the component is critical and unavailable.
17
+ - ``disabled`` — the component is not configured (missing env var).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ import platform
24
+ import sys
25
+ import time
26
+ from typing import Any, Dict, List
27
+
28
+ from mannf import __version__
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Data class
32
+ # ---------------------------------------------------------------------------
33
+
34
+
35
+ class ComponentResult:
36
+ """Structured result returned by each component health-check function."""
37
+
38
+ __slots__ = ("name", "status", "latency_ms", "details")
39
+
40
+ def __init__(
41
+ self,
42
+ name: str,
43
+ status: str,
44
+ latency_ms: float,
45
+ details: Dict[str, Any],
46
+ ) -> None:
47
+ self.name = name
48
+ self.status = status
49
+ self.latency_ms = latency_ms
50
+ self.details = details
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ return {
54
+ "name": self.name,
55
+ "status": self.status,
56
+ "latency_ms": round(self.latency_ms, 3),
57
+ "details": self.details,
58
+ }
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Individual component checks
63
+ # ---------------------------------------------------------------------------
64
+
65
+
66
+ async def check_database() -> ComponentResult:
67
+ """Probe the PostgreSQL database with a lightweight ``SELECT 1``."""
68
+ from mannf.product.database import _get_database_url
69
+
70
+ if _get_database_url() is None:
71
+ return ComponentResult(
72
+ name="database",
73
+ status="disabled",
74
+ latency_ms=0.0,
75
+ details={"reason": "DATABASE_URL not configured"},
76
+ )
77
+
78
+ t0 = time.perf_counter()
79
+ try:
80
+ from mannf.product.database import _build_engine
81
+ from sqlalchemy import text
82
+
83
+ engine = _build_engine()
84
+ if engine is None:
85
+ return ComponentResult(
86
+ name="database",
87
+ status="unhealthy",
88
+ latency_ms=0.0,
89
+ details={"error": "Engine could not be initialised"},
90
+ )
91
+
92
+ details: Dict[str, Any] = {"engine": "postgresql"}
93
+
94
+ # Collect pool stats when available
95
+ try:
96
+ pool = engine.pool
97
+ details["pool_size"] = pool.size()
98
+ details["pool_checked_out"] = pool.checkedout()
99
+ except Exception: # noqa: BLE001
100
+ pass
101
+
102
+ async with engine.connect() as conn:
103
+ await conn.execute(text("SELECT 1"))
104
+
105
+ latency_ms = (time.perf_counter() - t0) * 1000
106
+ return ComponentResult(
107
+ name="database",
108
+ status="healthy",
109
+ latency_ms=latency_ms,
110
+ details=details,
111
+ )
112
+ except Exception as exc: # noqa: BLE001
113
+ latency_ms = (time.perf_counter() - t0) * 1000
114
+ return ComponentResult(
115
+ name="database",
116
+ status="unhealthy",
117
+ latency_ms=latency_ms,
118
+ details={"error": str(exc)},
119
+ )
120
+
121
+
122
+ async def check_blob_storage() -> ComponentResult:
123
+ """Check whether Azure Blob Storage is configured and reachable."""
124
+ conn_string = os.environ.get("NAT_BLOB_STORAGE_CONNECTION_STRING", "").strip()
125
+ if not conn_string:
126
+ return ComponentResult(
127
+ name="blob_storage",
128
+ status="disabled",
129
+ latency_ms=0.0,
130
+ details={"reason": "NAT_BLOB_STORAGE_CONNECTION_STRING not configured"},
131
+ )
132
+
133
+ t0 = time.perf_counter()
134
+ try:
135
+ from azure.storage.blob import BlobServiceClient # type: ignore[import]
136
+
137
+ client = BlobServiceClient.from_connection_string(conn_string)
138
+ # Lightweight probe: list containers (first page only, max_results=1)
139
+ props = client.get_service_properties()
140
+ latency_ms = (time.perf_counter() - t0) * 1000
141
+ return ComponentResult(
142
+ name="blob_storage",
143
+ status="healthy",
144
+ latency_ms=latency_ms,
145
+ details={"provider": "azure_blob_storage"},
146
+ )
147
+ except ImportError:
148
+ latency_ms = (time.perf_counter() - t0) * 1000
149
+ return ComponentResult(
150
+ name="blob_storage",
151
+ status="degraded",
152
+ latency_ms=latency_ms,
153
+ details={
154
+ "reason": "azure-storage-blob SDK not installed",
155
+ "connection_string_set": True,
156
+ },
157
+ )
158
+ except Exception as exc: # noqa: BLE001
159
+ latency_ms = (time.perf_counter() - t0) * 1000
160
+ return ComponentResult(
161
+ name="blob_storage",
162
+ status="unhealthy",
163
+ latency_ms=latency_ms,
164
+ details={"error": str(exc)},
165
+ )
166
+
167
+
168
+ async def check_telemetry() -> ComponentResult:
169
+ """Report whether Application Insights is configured (informational only)."""
170
+ conn_string = os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING", "").strip()
171
+ if not conn_string:
172
+ return ComponentResult(
173
+ name="telemetry",
174
+ status="disabled",
175
+ latency_ms=0.0,
176
+ details={
177
+ "reason": "APPLICATIONINSIGHTS_CONNECTION_STRING not configured",
178
+ "connection_string_set": False,
179
+ },
180
+ )
181
+ return ComponentResult(
182
+ name="telemetry",
183
+ status="healthy",
184
+ latency_ms=0.0,
185
+ details={
186
+ "provider": "azure_application_insights",
187
+ "connection_string_set": True,
188
+ },
189
+ )
190
+
191
+
192
+ async def check_scan_queue(scans: Dict[str, Any]) -> ComponentResult:
193
+ """Report scan queue depth, running count, capacity, and max queue depth."""
194
+ t0 = time.perf_counter()
195
+ try:
196
+ # Import ScanStatus here to avoid circular imports at module level
197
+ from mannf.product.server import ScanStatus
198
+
199
+ running = sum(1 for s in scans.values() if s.get("status") == ScanStatus.RUNNING)
200
+ queued = sum(1 for s in scans.values() if s.get("status") == ScanStatus.PENDING)
201
+ capacity = int(os.environ.get("NAT_MAX_CONCURRENT_SCANS", "4"))
202
+ max_queue_depth = int(os.environ.get("NAT_MAX_QUEUE_DEPTH", "20"))
203
+
204
+ latency_ms = (time.perf_counter() - t0) * 1000
205
+ return ComponentResult(
206
+ name="scan_queue",
207
+ status="healthy",
208
+ latency_ms=latency_ms,
209
+ details={
210
+ "running": running,
211
+ "queued": queued,
212
+ "capacity": capacity,
213
+ "max_queue_depth": max_queue_depth,
214
+ },
215
+ )
216
+ except Exception as exc: # noqa: BLE001
217
+ latency_ms = (time.perf_counter() - t0) * 1000
218
+ return ComponentResult(
219
+ name="scan_queue",
220
+ status="degraded",
221
+ latency_ms=latency_ms,
222
+ details={"error": str(exc)},
223
+ )
224
+
225
+
226
+ def check_server_info(server_start_time: float) -> ComponentResult:
227
+ """Return static server metadata (synchronous — no I/O needed)."""
228
+ uptime_seconds = time.monotonic() - server_start_time
229
+ workers = int(os.environ.get("NAT_WORKERS", "1"))
230
+ host = os.environ.get("NAT_HOST", "0.0.0.0")
231
+ port = int(os.environ.get("NAT_PORT", "8080"))
232
+ return ComponentResult(
233
+ name="server",
234
+ status="healthy",
235
+ latency_ms=0.0,
236
+ details={
237
+ "version": __version__,
238
+ "python_version": sys.version,
239
+ "platform": platform.platform(),
240
+ "uptime_seconds": round(uptime_seconds, 1),
241
+ "workers": workers,
242
+ "host": host,
243
+ "port": port,
244
+ },
245
+ )
246
+
247
+
248
+ def check_environment() -> ComponentResult:
249
+ """Return environment and deployment metadata (synchronous — no I/O needed).
250
+
251
+ Exposes ``NAT_ENV``, canary status, the deployed git SHA, and the
252
+ deployment timestamp so that monitoring tools and the ``/api/v1/status``
253
+ response can clearly distinguish staging from production instances and
254
+ identify which revision is running.
255
+ """
256
+ nat_env = os.environ.get("NAT_ENV", "production")
257
+ is_canary = os.environ.get("CANARY_REVISION", "").lower() in ("true", "1", "yes")
258
+ deploy_sha = os.environ.get("DEPLOY_SHA", "")
259
+ deploy_timestamp = os.environ.get("DEPLOY_TIMESTAMP", "")
260
+ canary_weight_raw = os.environ.get("CANARY_TRAFFIC_WEIGHT", "")
261
+ canary_weight: Any = None
262
+ if canary_weight_raw:
263
+ try:
264
+ canary_weight = int(canary_weight_raw)
265
+ except ValueError:
266
+ canary_weight = canary_weight_raw
267
+
268
+ details: Dict[str, Any] = {
269
+ "environment": nat_env,
270
+ "is_staging": nat_env == "staging",
271
+ "is_canary": is_canary,
272
+ }
273
+ if deploy_sha:
274
+ details["deploy_sha"] = deploy_sha
275
+ if deploy_timestamp:
276
+ details["deploy_timestamp"] = deploy_timestamp
277
+ if is_canary and canary_weight is not None:
278
+ details["canary_traffic_weight"] = canary_weight
279
+
280
+ return ComponentResult(
281
+ name="environment",
282
+ status="healthy",
283
+ latency_ms=0.0,
284
+ details=details,
285
+ )
286
+
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # Aggregation helpers
290
+ # ---------------------------------------------------------------------------
291
+
292
+ _CRITICAL_COMPONENTS = {"database"}
293
+
294
+
295
+ def aggregate_status(components: List[ComponentResult]) -> str:
296
+ """Derive overall status from per-component results.
297
+
298
+ Rules
299
+ -----
300
+ - ``unhealthy`` if any **critical** component is unhealthy.
301
+ - ``degraded`` if any component is degraded.
302
+ - ``healthy`` if all components are healthy or disabled.
303
+ """
304
+ statuses = {c.name: c.status for c in components}
305
+
306
+ for name in _CRITICAL_COMPONENTS:
307
+ if statuses.get(name) == "unhealthy":
308
+ return "unhealthy"
309
+
310
+ if any(s == "degraded" for s in statuses.values()):
311
+ return "degraded"
312
+
313
+ if any(s == "unhealthy" for s in statuses.values()):
314
+ return "degraded"
315
+
316
+ return "healthy"
317
+
318
+
319
+ def check_billing() -> ComponentResult:
320
+ """Report whether Stripe billing is configured and ready.
321
+
322
+ Returns ``healthy`` when both ``STRIPE_SECRET_KEY`` and
323
+ ``STRIPE_WEBHOOK_SECRET`` are present. Returns ``degraded`` when only
324
+ the secret key is set (webhook not configured). Returns ``disabled``
325
+ when neither key is set — billing is optional for self-hosted deployments.
326
+ """
327
+ stripe_key = os.environ.get("STRIPE_SECRET_KEY", "").strip()
328
+ webhook_secret = os.environ.get("STRIPE_WEBHOOK_SECRET", "").strip()
329
+
330
+ if not stripe_key:
331
+ return ComponentResult(
332
+ name="billing",
333
+ status="disabled",
334
+ latency_ms=0.0,
335
+ details={
336
+ "reason": "STRIPE_SECRET_KEY not configured",
337
+ "stripe_key_set": False,
338
+ "webhook_secret_set": bool(webhook_secret),
339
+ },
340
+ )
341
+
342
+ if not webhook_secret:
343
+ return ComponentResult(
344
+ name="billing",
345
+ status="degraded",
346
+ latency_ms=0.0,
347
+ details={
348
+ "reason": "STRIPE_WEBHOOK_SECRET not configured — webhook verification disabled",
349
+ "stripe_key_set": True,
350
+ "webhook_secret_set": False,
351
+ "stripe_mode": "test" if stripe_key.startswith("sk_test_") else "live",
352
+ },
353
+ )
354
+
355
+ return ComponentResult(
356
+ name="billing",
357
+ status="healthy",
358
+ latency_ms=0.0,
359
+ details={
360
+ "stripe_key_set": True,
361
+ "webhook_secret_set": True,
362
+ "stripe_mode": "test" if stripe_key.startswith("sk_test_") else "live",
363
+ },
364
+ )
365
+
366
+
367
+ def check_demo_mode() -> ComponentResult:
368
+ """Report whether NAT demo mode is active.
369
+
370
+ Demo mode is controlled by the ``NAT_DEMO_MODE`` environment variable.
371
+ When active, Stripe calls use test keys, scan results use synthetic data
372
+ generators, and rate limits are relaxed for demo walkthroughs.
373
+ """
374
+ raw = os.environ.get("NAT_DEMO_MODE", "").lower().strip()
375
+ is_demo = raw in ("true", "1", "yes")
376
+
377
+ return ComponentResult(
378
+ name="demo_mode",
379
+ status="healthy",
380
+ latency_ms=0.0,
381
+ details={
382
+ "demo_mode_active": is_demo,
383
+ "nat_demo_mode": raw or "false",
384
+ },
385
+ )
386
+
387
+
388
+ async def run_all_checks(scans: Dict[str, Any], server_start_time: float) -> List[ComponentResult]:
389
+ """Run all component checks and return a list of results."""
390
+ import asyncio
391
+
392
+ results = await asyncio.gather(
393
+ check_database(),
394
+ check_blob_storage(),
395
+ check_telemetry(),
396
+ check_scan_queue(scans),
397
+ return_exceptions=False,
398
+ )
399
+ results = list(results)
400
+ results.append(check_server_info(server_start_time))
401
+ results.append(check_environment())
402
+ results.append(check_billing())
403
+ results.append(check_demo_mode())
404
+ return results
@@ -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
+ """Scan artifact blob storage package."""
7
+
8
+ from mannf.product.storage.artifact_store import ArtifactStore
9
+
10
+ __all__ = ["ArtifactStore"]