anta 1.10.0.dev5__tar.gz → 1.10.0.dev6__tar.gz

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 (156) hide show
  1. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/PKG-INFO +1 -1
  2. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/__init__.py +0 -2
  3. anta-1.10.0.dev6/anta/_advisory/findings/assessment.py +48 -0
  4. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/reporter/reporting.py +10 -19
  5. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/results.py +31 -0
  6. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/status.py +6 -11
  7. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_eos/platform.py +30 -8
  8. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/utils.py +5 -5
  9. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/reporter/md_reporter.py +7 -35
  10. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/reporter/table_reporter.py +3 -11
  11. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/result_manager/__init__.py +11 -15
  12. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/result_manager/models.py +3 -21
  13. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/aaa.py +4 -5
  14. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/advisories/sa_117.py +6 -12
  15. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/advisories/sa_140.py +7 -15
  16. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/advisories/sa_142.py +3 -3
  17. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/advisories/sa_146.py +3 -11
  18. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/advisories/sa_147.py +14 -31
  19. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/interfaces.py +3 -0
  20. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta.egg-info/PKG-INFO +1 -1
  21. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta.egg-info/SOURCES.txt +1 -0
  22. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/pyproject.toml +4 -4
  23. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/LICENSE +0 -0
  24. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/README.md +0 -0
  25. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/__init__.py +0 -0
  26. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/base.py +0 -0
  27. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/eos_versions.py +0 -0
  28. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/__init__.py +0 -0
  29. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/eos.py +0 -0
  30. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/management.py +0 -0
  31. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/models.py +0 -0
  32. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/platform.py +0 -0
  33. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/redirection.py +0 -0
  34. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/software.py +0 -0
  35. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/ssh.py +0 -0
  36. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/facts/terminattr.py +0 -0
  37. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/findings/__init__.py +0 -0
  38. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/findings/models.py +0 -0
  39. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/findings/projection.py +0 -0
  40. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/models.py +0 -0
  41. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/optional_commands.py +0 -0
  42. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/remediation.py +0 -0
  43. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/reporter/__init__.py +0 -0
  44. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/reporter/csv_reporter.py +0 -0
  45. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/reporter/md_reporter.py +0 -0
  46. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_advisory/version.py +0 -0
  47. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_eos/__init__.py +0 -0
  48. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_eos/parsing.py +0 -0
  49. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_eos/version.py +0 -0
  50. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/_runner.py +0 -0
  51. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/catalog.py +0 -0
  52. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/__init__.py +0 -0
  53. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/_main.py +0 -0
  54. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/check/__init__.py +0 -0
  55. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/check/commands.py +0 -0
  56. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/console.py +0 -0
  57. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/debug/__init__.py +0 -0
  58. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/debug/commands.py +0 -0
  59. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/debug/utils.py +0 -0
  60. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/exec/__init__.py +0 -0
  61. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/exec/commands.py +0 -0
  62. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/exec/utils.py +0 -0
  63. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/get/__init__.py +0 -0
  64. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/get/commands.py +0 -0
  65. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/get/utils.py +0 -0
  66. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/nrfu/__init__.py +0 -0
  67. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/nrfu/commands.py +0 -0
  68. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/nrfu/utils.py +0 -0
  69. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/cli/psirt.py +0 -0
  70. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/constants.py +0 -0
  71. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/custom_types.py +0 -0
  72. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/decorators.py +0 -0
  73. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/device.py +0 -0
  74. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/__init__.py +0 -0
  75. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/avt.py +0 -0
  76. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/bfd.py +0 -0
  77. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/configuration.py +0 -0
  78. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/connectivity.py +0 -0
  79. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/cvx.py +0 -0
  80. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/evpn.py +0 -0
  81. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/flow_tracking.py +0 -0
  82. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/hardware.py +0 -0
  83. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/interfaces.py +0 -0
  84. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/logging.py +0 -0
  85. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/path_selection.py +0 -0
  86. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/routing/__init__.py +0 -0
  87. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/routing/bgp.py +0 -0
  88. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/routing/generic.py +0 -0
  89. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/routing/isis.py +0 -0
  90. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/routing/ospf.py +0 -0
  91. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/security.py +0 -0
  92. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/services.py +0 -0
  93. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/snmp.py +0 -0
  94. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/stun.py +0 -0
  95. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/system.py +0 -0
  96. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/input_models/vlan.py +0 -0
  97. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/inventory/__init__.py +0 -0
  98. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/inventory/exceptions.py +0 -0
  99. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/inventory/models.py +0 -0
  100. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/logger.py +0 -0
  101. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/models.py +0 -0
  102. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/py.typed +0 -0
  103. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/reporter/__init__.py +0 -0
  104. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/reporter/csv_reporter.py +0 -0
  105. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/reporter/jinja_reporter.py +0 -0
  106. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/runner.py +0 -0
  107. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/settings.py +0 -0
  108. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/__init__.py +0 -0
  109. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/advisories/__init__.py +0 -0
  110. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/avt.py +0 -0
  111. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/bfd.py +0 -0
  112. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/configuration.py +0 -0
  113. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/connectivity.py +0 -0
  114. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/cvx.py +0 -0
  115. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/evpn.py +0 -0
  116. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/field_notices.py +0 -0
  117. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/flow_tracking.py +0 -0
  118. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/greent.py +0 -0
  119. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/hardware.py +0 -0
  120. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/lanz.py +0 -0
  121. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/logging.py +0 -0
  122. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/mlag.py +0 -0
  123. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/multicast.py +0 -0
  124. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/path_selection.py +0 -0
  125. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/profiles.py +0 -0
  126. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/ptp.py +0 -0
  127. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/routing/__init__.py +0 -0
  128. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/routing/bgp.py +0 -0
  129. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/routing/generic.py +0 -0
  130. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/routing/isis.py +0 -0
  131. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/routing/ospf.py +0 -0
  132. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/security.py +0 -0
  133. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/services.py +0 -0
  134. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/snmp.py +0 -0
  135. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/software.py +0 -0
  136. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/stp.py +0 -0
  137. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/stun.py +0 -0
  138. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/system.py +0 -0
  139. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/vlan.py +0 -0
  140. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tests/vxlan.py +0 -0
  141. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta/tools.py +0 -0
  142. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta.egg-info/dependency_links.txt +0 -0
  143. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta.egg-info/entry_points.txt +0 -0
  144. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta.egg-info/requires.txt +0 -0
  145. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/anta.egg-info/top_level.txt +0 -0
  146. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/__init__.py +0 -0
  147. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/_auth.py +0 -0
  148. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/_constants.py +0 -0
  149. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/_errors.py +0 -0
  150. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/_models.py +0 -0
  151. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/_types.py +0 -0
  152. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/aio_portcheck.py +0 -0
  153. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/config_session.py +0 -0
  154. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/device.py +0 -0
  155. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/asynceapi/errors.py +0 -0
  156. {anta-1.10.0.dev5 → anta-1.10.0.dev6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anta
3
- Version: 1.10.0.dev5
3
+ Version: 1.10.0.dev6
4
4
  Summary: Arista Network Test Automation (ANTA) Framework
5
5
  Author-email: Arista Networks ANTA maintainers <anta-dev@arista.com>
6
6
  Maintainer-email: Arista Networks ANTA maintainers <anta-dev@arista.com>, Khelil Sator <ksator@arista.com>, Matthieu Tâche <mtache@arista.com>, Thomas Grimonet <tgrimonet@arista.com>, Guillaume Mulocher <gmulocher@arista.com>, Carl Baillargeon <carl.baillargeon@arista.com>
@@ -33,7 +33,6 @@ class RICH_COLOR_PALETTE:
33
33
 
34
34
  ERROR = "indian_red"
35
35
  FAILURE = "bold red"
36
- INCONCLUSIVE = "bold orange3"
37
36
  SUCCESS = "green4"
38
37
  SKIPPED = "bold orange4"
39
38
  HEADER = "cyan"
@@ -44,7 +43,6 @@ class RICH_COLOR_PALETTE:
44
43
  RICH_COLOR_THEME = {
45
44
  "success": RICH_COLOR_PALETTE.SUCCESS,
46
45
  "skipped": RICH_COLOR_PALETTE.SKIPPED,
47
- "inconclusive": RICH_COLOR_PALETTE.INCONCLUSIVE,
48
46
  "failure": RICH_COLOR_PALETTE.FAILURE,
49
47
  "error": RICH_COLOR_PALETTE.ERROR,
50
48
  "unset": RICH_COLOR_PALETTE.UNSET,
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2026 Arista Networks, Inc.
2
+ # Use of this source code is governed by the Apache License 2.0
3
+ # that can be found in the LICENSE file.
4
+ """Shared assessment steps for structured advisory findings."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ from anta._advisory.eos_versions import AffectedStatus, evaluate_version
11
+ from anta._advisory.facts.models import Fact, FactProblemKind, UnavailableFact
12
+ from anta._advisory.findings.models import EosReleaseAssessment, ErrorResult, NotAffectedResult, VersionRelation
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Sequence
16
+
17
+ from anta._advisory.eos_versions import VersionRule
18
+ from anta._eos.version import EOSVersion
19
+
20
+
21
+ def assess_eos_scope(
22
+ vulnerability_id: str,
23
+ version: Fact[EOSVersion],
24
+ affected_versions: Sequence[VersionRule],
25
+ ) -> EosReleaseAssessment | ErrorResult | NotAffectedResult:
26
+ """Return an affected EOS assessment or a terminal error or not-affected result."""
27
+ release = assess_eos_version(version, affected_versions)
28
+ if isinstance(release, UnavailableFact):
29
+ return ErrorResult(vulnerability_id=vulnerability_id, problems=(release,))
30
+ if release.relation is VersionRelation.OUTSIDE_SCOPE:
31
+ return NotAffectedResult(vulnerability_id=vulnerability_id, decisive=(release,))
32
+ return release
33
+
34
+
35
+ def assess_eos_version(
36
+ version: Fact[EOSVersion],
37
+ affected_versions: Sequence[VersionRule],
38
+ ) -> EosReleaseAssessment | UnavailableFact[EOSVersion]:
39
+ """Interpret an EOS version fact without making a terminal vulnerability decision."""
40
+ if isinstance(version, UnavailableFact):
41
+ return version
42
+
43
+ evaluation = evaluate_version(version.value, affected_versions)
44
+ if evaluation.affected_status is AffectedStatus.UNKNOWN:
45
+ return version.definition.unavailable(FactProblemKind.INVALID, version.source)
46
+
47
+ relation = VersionRelation.AFFECTED if evaluation.affected_status is AffectedStatus.AFFECTED else VersionRelation.OUTSIDE_SCOPE
48
+ return EosReleaseAssessment(version, relation)
@@ -10,7 +10,7 @@ from dataclasses import dataclass, field
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
12
  from anta._advisory.models import _ADVISORY_VULNERABILITY_SEVERITY_RANK, _AdvisoryVulnerabilitySeverity
13
- from anta._advisory.results import _get_advisory_metadata
13
+ from anta._advisory.results import _get_advisory_metadata, _get_advisory_status
14
14
  from anta.logger import anta_log_exception
15
15
  from anta.result_manager.models import AntaTestStatus
16
16
 
@@ -26,27 +26,16 @@ if TYPE_CHECKING:
26
26
 
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
- _ADVISORY_RESULT_RANK = {
30
- AntaTestStatus.FAILURE: 0,
31
- AntaTestStatus.INCONCLUSIVE: 1,
32
- AntaTestStatus.SUCCESS: 2,
33
- AntaTestStatus.ERROR: 3,
34
- AntaTestStatus.SKIPPED: 4,
35
- AntaTestStatus.UNSET: 5,
36
- }
29
+ _ADVISORY_RESULT_RANK = {"affected": 0, "inconclusive": 1, "mitigated": 2, "not affected": 3, "error": 4, "skipped": 5, "unset": 6}
37
30
 
38
31
 
39
32
  def _get_advisory_result(result: TestResult | AtomicTestResult) -> str:
40
- """Translate an ANTA status to advisory-facing result wording."""
41
- # MITIGATED is projected to native INCONCLUSIVE until the semantic state is retained on the atomic
42
- # result. Reporters follow that native status and do not recover mitigation from inconclusive
43
- # message text. SUCCESS remains accepted for results that used the earlier success-based encoding.
44
- if result.result is AntaTestStatus.SUCCESS:
45
- mitigated_opening = "The device is affected but mitigated because "
46
- return "mitigated" if any(mitigated_opening in message for message in result.messages) else "not affected"
33
+ """Translate retained advisory status or an ANTA lifecycle status to report wording."""
34
+ if (status := _get_advisory_status(result)) is not None:
35
+ return status.value.replace("_", " ")
47
36
  return {
48
37
  AntaTestStatus.UNSET: "unset",
49
- AntaTestStatus.INCONCLUSIVE: "inconclusive",
38
+ AntaTestStatus.SUCCESS: "not affected",
50
39
  AntaTestStatus.FAILURE: "affected",
51
40
  AntaTestStatus.ERROR: "error",
52
41
  AntaTestStatus.SKIPPED: "skipped",
@@ -169,7 +158,9 @@ def group_advisory_results(results: Sequence[TestResult]) -> tuple[AdvisoryResul
169
158
  else:
170
159
  group_results = []
171
160
  groups[advisory.sa_number] = (advisory, group_results)
172
- sorted_result = result.model_copy(update={"atomic_results": sorted(result.atomic_results, key=lambda atomic: _ADVISORY_RESULT_RANK[atomic.result])})
161
+ sorted_result = result.model_copy(
162
+ update={"atomic_results": sorted(result.atomic_results, key=lambda atomic: _ADVISORY_RESULT_RANK[_get_advisory_result(atomic)])}
163
+ )
173
164
  group_results.append(sorted_result)
174
165
 
175
166
  result_groups = (
@@ -178,7 +169,7 @@ def group_advisory_results(results: Sequence[TestResult]) -> tuple[AdvisoryResul
178
169
  results=tuple(
179
170
  sorted(
180
171
  group_results,
181
- key=lambda result: (_ADVISORY_RESULT_RANK[result.result], result.name.casefold(), result.test.casefold()),
172
+ key=lambda result: (_ADVISORY_RESULT_RANK[_get_advisory_result(result)], result.name.casefold(), result.test.casefold()),
182
173
  )
183
174
  ),
184
175
  )
@@ -9,12 +9,22 @@ from pydantic import Field
9
9
 
10
10
  from anta._advisory.models import _AdvisoryMetadata # noqa: TC001 # Pydantic resolves this annotation at runtime.
11
11
  from anta._advisory.remediation import RemediationGuidance, RemediationPlan # noqa: TC001 # Pydantic resolves these annotations at runtime.
12
+ from anta._advisory.status import AdvisoryStatus
12
13
  from anta.result_manager.models import AntaTestStatus, AtomicTestResult, TestResult
13
14
 
15
+ _ADVISORY_STATUS_PRIORITY = {
16
+ AdvisoryStatus.NOT_AFFECTED: 0,
17
+ AdvisoryStatus.MITIGATED: 1,
18
+ AdvisoryStatus.INCONCLUSIVE: 2,
19
+ AdvisoryStatus.AFFECTED: 3,
20
+ AdvisoryStatus.ERROR: 4,
21
+ }
22
+
14
23
 
15
24
  class _AdvisoryAtomicTestResult(AtomicTestResult):
16
25
  """Atomic advisory result with optional vulnerability association and remediation."""
17
26
 
27
+ advisory_status: AdvisoryStatus | None = Field(default=None, exclude=True)
18
28
  vulnerability_ids: tuple[str, ...] | None = Field(default=None, exclude=True)
19
29
  remediation: RemediationPlan | None = Field(default=None, exclude=True)
20
30
  remediation_guidance: frozenset[RemediationGuidance] = Field(default_factory=frozenset, exclude=True)
@@ -25,6 +35,18 @@ class _AdvisoryTestResult(TestResult):
25
35
 
26
36
  advisory: _AdvisoryMetadata = Field(exclude=True)
27
37
 
38
+ @property
39
+ def advisory_status(self) -> AdvisoryStatus | None:
40
+ """Return the highest-priority semantic status from this advisory result."""
41
+ if self.result is AntaTestStatus.ERROR:
42
+ return AdvisoryStatus.ERROR
43
+ statuses = (atomic.advisory_status for atomic in self.atomic_results if isinstance(atomic, _AdvisoryAtomicTestResult))
44
+ return max(
45
+ (status for status in statuses if status is not None),
46
+ key=_ADVISORY_STATUS_PRIORITY.__getitem__,
47
+ default=None,
48
+ )
49
+
28
50
  def add(
29
51
  self,
30
52
  description: str,
@@ -68,6 +90,15 @@ def _get_advisory_metadata(result: TestResult) -> _AdvisoryMetadata | None:
68
90
  return result.advisory if isinstance(result, _AdvisoryTestResult) else None
69
91
 
70
92
 
93
+ def _get_advisory_status(result: TestResult | AtomicTestResult) -> AdvisoryStatus | None:
94
+ """Return the semantic status from an advisory result, otherwise None."""
95
+ if isinstance(result, _AdvisoryTestResult):
96
+ return result.advisory_status
97
+ if isinstance(result, _AdvisoryAtomicTestResult):
98
+ return result.advisory_status
99
+ return None
100
+
101
+
71
102
  def _get_atomic_vulnerability_ids(result: AtomicTestResult) -> tuple[str, ...] | None:
72
103
  """Return explicitly associated vulnerability IDs from an advisory atomic result."""
73
104
  return result.vulnerability_ids if isinstance(result, _AdvisoryAtomicTestResult) else None
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2026 Arista Networks, Inc.
2
2
  # Use of this source code is governed by the Apache License 2.0
3
3
  # that can be found in the LICENSE file.
4
- """Semantic advisory statuses and their temporary ANTA projection."""
4
+ """Semantic advisory statuses and their ANTA projection."""
5
5
 
6
6
  from __future__ import annotations
7
7
 
@@ -34,12 +34,7 @@ def project_advisory_status(
34
34
  message: str,
35
35
  remediation: RemediationPlan | None,
36
36
  ) -> None:
37
- """Attach remediation and project a semantic advisory status onto ANTA.
38
-
39
- MITIGATED remains distinct throughout assessment and is projected to INCONCLUSIVE only
40
- because ANTA does not yet expose a native mitigated status. When it does, this function
41
- is the single compatibility boundary that must change.
42
- """
37
+ """Retain an advisory status, attach remediation, and project it onto ANTA."""
43
38
  requires_remediation = status in {AdvisoryStatus.AFFECTED, AdvisoryStatus.MITIGATED, AdvisoryStatus.INCONCLUSIVE}
44
39
  if requires_remediation != (remediation is not None):
45
40
  requirement = "requires" if requires_remediation else "must not include"
@@ -53,12 +48,12 @@ def project_advisory_status(
53
48
  guidance.add(RemediationGuidance.UNRESOLVED_CONDITIONS)
54
49
  result.remediation_guidance = frozenset(guidance)
55
50
 
56
- if status is AdvisoryStatus.NOT_AFFECTED:
51
+ result.advisory_status = status
52
+
53
+ if status in {AdvisoryStatus.NOT_AFFECTED, AdvisoryStatus.MITIGATED}:
57
54
  result.is_success(message)
58
- elif status is AdvisoryStatus.AFFECTED:
55
+ elif status in {AdvisoryStatus.AFFECTED, AdvisoryStatus.INCONCLUSIVE}:
59
56
  result.is_failure(message)
60
- elif status in {AdvisoryStatus.MITIGATED, AdvisoryStatus.INCONCLUSIVE}:
61
- result.is_inconclusive(message)
62
57
  elif status is AdvisoryStatus.ERROR:
63
58
  result.is_error(message)
64
59
  else:
@@ -64,13 +64,18 @@ class PlatformFamily(str, Enum):
64
64
  """
65
65
 
66
66
  SERIES_720_D = "720D Series"
67
+ SERIES_720_DF = "720DF Series"
68
+ SERIES_720_DP = "720DP Series"
69
+ SERIES_720_DT = "720DT Series"
67
70
  SERIES_720_XP = "720XP Series"
68
71
  SERIES_720_XDM = "720XDM Series"
69
72
  SERIES_722_XPM = "722XPM Series"
70
- SERIES_755_758 = "755/758 Series"
73
+ SERIES_750 = "750 Series"
74
+ SERIES_750_X = "750X Series"
71
75
  SERIES_710 = "710 Series"
72
76
  SERIES_7010 = "7010 Series"
73
77
  SERIES_7010_X = "7010X Series"
78
+ SERIES_7010_TX = "7010TX Series"
74
79
  SERIES_7020_R = "7020R Series"
75
80
  SERIES_7020_R4 = "7020R4 Series"
76
81
  SERIES_7130 = "7130 Series"
@@ -90,6 +95,7 @@ class PlatformFamily(str, Enum):
90
95
  SERIES_7260_X = "7260X Series"
91
96
  SERIES_7260_X3 = "7260X3 Series"
92
97
  SERIES_7280_E = "7280E Series"
98
+ SERIES_7280_SE = "7280SE Series"
93
99
  SERIES_7280_R = "7280R Series"
94
100
  SERIES_7280_R2 = "7280R2 Series"
95
101
  SERIES_7280_R3 = "7280R3 Series"
@@ -114,7 +120,7 @@ class PlatformFamily(str, Enum):
114
120
  CLOUDEOS = "CloudEOS"
115
121
  CEOS_LAB = "cEOS-lab"
116
122
  VEOS_LAB = "vEOS-lab"
117
- CLOUDVISION_EXCHANGE = "CloudVision eXchange"
123
+ CVX = "CloudVision eXchange"
118
124
 
119
125
 
120
126
  @dataclass(frozen=True, slots=True)
@@ -213,14 +219,16 @@ def _module_rule(role: PlatformComponentRole, *patterns: str) -> _ModuleFamilyRu
213
219
 
214
220
 
215
221
  SYSTEM_PLATFORM_RULES: tuple[_SystemPlatformRule, ...] = (
216
- _system_rule(PlatformType.FIXED, r"^CCS-720D[FTP]-.*$", families=(PlatformFamily.SERIES_720_D,)),
222
+ _system_rule(PlatformType.FIXED, r"^CCS-720DF-.*$", families=(PlatformFamily.SERIES_720_D, PlatformFamily.SERIES_720_DF)),
223
+ _system_rule(PlatformType.FIXED, r"^CCS-720DP-.*$", families=(PlatformFamily.SERIES_720_D, PlatformFamily.SERIES_720_DP)),
224
+ _system_rule(PlatformType.FIXED, r"^CCS-720DT-.*$", families=(PlatformFamily.SERIES_720_D, PlatformFamily.SERIES_720_DT)),
217
225
  _system_rule(PlatformType.FIXED, r"^CCS-720XP-.*$", families=(PlatformFamily.SERIES_720_XP,)),
218
226
  _system_rule(PlatformType.FIXED, r"^CCS-720XDM-.*$", families=(PlatformFamily.SERIES_720_XDM,)),
219
227
  _system_rule(PlatformType.FIXED, r"^CCS-72[02]XPM-.*$", families=(PlatformFamily.SERIES_722_XPM,)),
220
- _system_rule(PlatformType.CHASSIS, r"^CCS-75[58]-CH.*$", families=(PlatformFamily.SERIES_755_758,)),
228
+ _system_rule(PlatformType.CHASSIS, r"^CCS-75[58]-CH.*$", families=(PlatformFamily.SERIES_750, PlatformFamily.SERIES_750_X)),
221
229
  _system_rule(PlatformType.FIXED, r"^CCS-710[A-Z0-9]*-.*$", families=(PlatformFamily.SERIES_710,)),
222
230
  _system_rule(PlatformType.FIXED, r"^DCS-7010T-.*$", families=(PlatformFamily.SERIES_7010,)),
223
- _system_rule(PlatformType.FIXED, r"^DCS-7010TX-.*$", families=(PlatformFamily.SERIES_7010_X,)),
231
+ _system_rule(PlatformType.FIXED, r"^DCS-7010TX-.*$", families=(PlatformFamily.SERIES_7010_X, PlatformFamily.SERIES_7010_TX)),
224
232
  _system_rule(PlatformType.FIXED, r"^DCS-7020[ST]R[A-Z]*-.*$", families=(PlatformFamily.SERIES_7020_R,)),
225
233
  _system_rule(PlatformType.FIXED, r"^DCS-7020H?R4M?-.*$", families=(PlatformFamily.SERIES_7020_R4,)),
226
234
  _system_rule(PlatformType.FIXED, r"^DCS-7050[A-Z]*X(?!\d).*$", families=(PlatformFamily.SERIES_7050_X,)),
@@ -239,7 +247,7 @@ SYSTEM_PLATFORM_RULES: tuple[_SystemPlatformRule, ...] = (
239
247
  _system_rule(PlatformType.FIXED, r"^DCS-7250[A-WY-Z]*X.*$", families=(PlatformFamily.SERIES_7250_X,)),
240
248
  _system_rule(PlatformType.FIXED, r"^DCS-7260[A-Z]*X(?!\d).*$", families=(PlatformFamily.SERIES_7260_X,)),
241
249
  _system_rule(PlatformType.FIXED, r"^DCS-7260[A-Z]*X3.*$", families=(PlatformFamily.SERIES_7260_X3,)),
242
- _system_rule(PlatformType.FIXED, r"^DCS-7280SE-.*$", families=(PlatformFamily.SERIES_7280_E,)),
250
+ _system_rule(PlatformType.FIXED, r"^DCS-7280SE-.*$", families=(PlatformFamily.SERIES_7280_E, PlatformFamily.SERIES_7280_SE)),
243
251
  _system_rule(PlatformType.FIXED, r"^DCS-7280[CQST]R(?!\d).*$", families=(PlatformFamily.SERIES_7280_R,)),
244
252
  _system_rule(PlatformType.FIXED, r"^DCS-7280[CS]R2.*$", families=(PlatformFamily.SERIES_7280_R2,)),
245
253
  _system_rule(PlatformType.FIXED, r"^DCS-7280[CDPST]R3.*$", families=(PlatformFamily.SERIES_7280_R3,)),
@@ -258,19 +266,33 @@ SYSTEM_PLATFORM_RULES: tuple[_SystemPlatformRule, ...] = (
258
266
  _system_rule(PlatformType.VIRTUAL, r"^CLOUDEOS$", families=(PlatformFamily.CLOUDEOS,)),
259
267
  _system_rule(PlatformType.VIRTUAL, r"^(?:CEOS-LAB|CEOSLAB)(?:-.+)?$", families=(PlatformFamily.CEOS_LAB,)),
260
268
  _system_rule(PlatformType.VIRTUAL, r"^(?:VEOS-LAB|VEOSLAB)(?:-.+)?$", families=(PlatformFamily.VEOS_LAB,)),
261
- _system_rule(PlatformType.APPLIANCE, r"^CLOUDVISION EXCHANGE$", families=(PlatformFamily.CLOUDVISION_EXCHANGE,)),
269
+ # TODO(platform-cvx-cloudeos): Newer CloudEOS releases also report "vEOS".
270
+ # Find an additional device property that distinguishes them before relying
271
+ # on the model string alone for CVX versus CloudEOS classification.
272
+ _system_rule(PlatformType.VIRTUAL, r"^VEOS$", families=(PlatformFamily.CVX,)),
262
273
  )
263
274
 
264
275
 
265
276
  MODULE_PLATFORM_FAMILY_RULES: dict[PlatformFamily, tuple[_ModuleFamilyRule, ...]] = {
277
+ PlatformFamily.SERIES_750: (
278
+ _module_rule(PlatformComponentRole.SUPERVISOR, r"^CCS-750-SUP(?:25|100)(?:-CLK)?$"),
279
+ _module_rule(PlatformComponentRole.SWITCH_CARD, r"^CCS-75[58]-X3-SC$"),
280
+ _module_rule(PlatformComponentRole.LINE_CARD, r"^CCS-750X(?:M|PM)?-.*-LC$"),
281
+ ),
282
+ PlatformFamily.SERIES_750_X: (
283
+ _module_rule(PlatformComponentRole.SUPERVISOR, r"^CCS-750-SUP(?:25|100)(?:-CLK)?$"),
284
+ _module_rule(PlatformComponentRole.SWITCH_CARD, r"^CCS-75[58]-X3-SC$"),
285
+ _module_rule(PlatformComponentRole.LINE_CARD, r"^CCS-750X(?:M|PM)?-.*-LC$"),
286
+ ),
266
287
  PlatformFamily.SERIES_7289_R3A: (_module_rule(PlatformComponentRole.SWITCH_CARD, r"^(?:DCS-)?7289R3A[A-Z]*-SC$"),),
267
288
  PlatformFamily.SERIES_7300_X: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7300X(?!\d)-.*-LC$"),),
268
289
  PlatformFamily.SERIES_7300_X3: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7300X3-.*-LC$"),),
290
+ PlatformFamily.SERIES_7320_X: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7320X-.*-LC$"),),
269
291
  PlatformFamily.SERIES_7358_X4: (_module_rule(PlatformComponentRole.SWITCH_CARD, r"^(?:DCS-)?7358X4-SC$"),),
270
292
  PlatformFamily.SERIES_7368_X4: (_module_rule(PlatformComponentRole.SWITCH_CARD, r"^(?:DCS-)?7368X4-SC$"),),
271
293
  PlatformFamily.SERIES_7388_X5: (_module_rule(PlatformComponentRole.SWITCH_CARD, r"^(?:DCS-)?7388X5-SC$"),),
272
294
  PlatformFamily.SERIES_7500_E: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7500E-.*-LC$"),),
273
- PlatformFamily.SERIES_7500_R: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7500R(?!\d)-.*-LC$"),),
295
+ PlatformFamily.SERIES_7500_R: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7500R(?!\d)[A-Z]*-.*-LC$"),),
274
296
  PlatformFamily.SERIES_7500_R2: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7500R2[A-Z]*-.*-LC$"),),
275
297
  PlatformFamily.SERIES_7500_R3: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7500R3[A-Z]*-.*-LC$"),),
276
298
  PlatformFamily.SERIES_7800_R3: (_module_rule(PlatformComponentRole.LINE_CARD, r"^(?:DCS-)?7800R3[A-Z]*-.*-LC$"),),
@@ -63,14 +63,14 @@ def parse_tags(_ctx: click.Context, _param: Option, value: str | None) -> set[st
63
63
  def exit_with_code(ctx: click.Context) -> None:
64
64
  """Exit the Click application with an exit code.
65
65
 
66
- This function determines the global test status to be either `unset`, `skipped`, `success`, `inconclusive`, `failure`, or `error`
66
+ This function determines the global test status to be either `unset`, `skipped`, `success`, `failure` or `error`
67
67
  from the `ResultManager` instance.
68
68
  If flag `ignore_error` is set, the `error` status will be ignored in all the tests.
69
69
  If flag `ignore_status` is set, the exit code will always be 0.
70
70
  Exit the application with the following exit code:
71
71
  * 0 if `ignore_status` is `True` or global test status is `unset`, `skipped` or `success`
72
- * 4 if status is `inconclusive` or `failure`
73
- * 3 if status is `error`.
72
+ * 3 if status is `error`
73
+ * 4 if status is `failure`.
74
74
 
75
75
  Parameters
76
76
  ----------
@@ -86,7 +86,7 @@ def exit_with_code(ctx: click.Context) -> None:
86
86
 
87
87
  if status in {"unset", "skipped", "success"}:
88
88
  ctx.exit(ExitCode.OK)
89
- if status in {"inconclusive", "failure"}:
89
+ if status == "failure":
90
90
  ctx.exit(ExitCode.TESTS_FAILED)
91
91
  if status == "error":
92
92
  ctx.exit(ExitCode.TESTS_ERROR)
@@ -133,7 +133,7 @@ def result_options(f: Callable[..., R]) -> Callable[..., R]:
133
133
  default=None,
134
134
  type=click.Choice(_HIDE_STATUS, case_sensitive=False),
135
135
  multiple=True,
136
- help="Hide results by type: success / inconclusive / failure / error / skipped.",
136
+ help="Hide results by type: success / failure / error / skipped.",
137
137
  required=False,
138
138
  )(f)
139
139
  f = click.option(
@@ -43,7 +43,6 @@ STATUS_MAP = {
43
43
  AntaTestStatus.FAILURE: "❌&nbsp;Failure",
44
44
  AntaTestStatus.ERROR: "❗&nbsp;Error",
45
45
  AntaTestStatus.SKIPPED: "⏭️&nbsp;Skipped",
46
- AntaTestStatus.INCONCLUSIVE: "❓&nbsp;Inconclusive",
47
46
  AntaTestStatus.UNSET: "Unset",
48
47
  }
49
48
  """Mapping of `AntaTestStatus` to their string representation with icons and non-breaking spaces for Markdown."""
@@ -436,7 +435,6 @@ class SummaryTotals(MDReportBase):
436
435
  TOTAL_TESTS,
437
436
  STATUS_MAP[AntaTestStatus.SUCCESS],
438
437
  STATUS_MAP[AntaTestStatus.SKIPPED],
439
- STATUS_MAP[AntaTestStatus.INCONCLUSIVE],
440
438
  STATUS_MAP[AntaTestStatus.FAILURE],
441
439
  STATUS_MAP[AntaTestStatus.ERROR],
442
440
  ]
@@ -449,7 +447,6 @@ class SummaryTotals(MDReportBase):
449
447
  f"| {self.results.get_total_results()} "
450
448
  f"| {self.results.get_total_results({AntaTestStatus.SUCCESS})} "
451
449
  f"| {self.results.get_total_results({AntaTestStatus.SKIPPED})} "
452
- f"| {self.results.get_total_results({AntaTestStatus.INCONCLUSIVE})} "
453
450
  f"| {self.results.get_total_results({AntaTestStatus.FAILURE})} "
454
451
  f"| {self.results.get_total_results({AntaTestStatus.ERROR})} |\n"
455
452
  )
@@ -470,7 +467,6 @@ class SummaryTotalsDeviceUnderTest(MDReportBase):
470
467
  TOTAL_TESTS,
471
468
  STATUS_MAP[AntaTestStatus.SUCCESS],
472
469
  STATUS_MAP[AntaTestStatus.SKIPPED],
473
- STATUS_MAP[AntaTestStatus.INCONCLUSIVE],
474
470
  STATUS_MAP[AntaTestStatus.FAILURE],
475
471
  STATUS_MAP[AntaTestStatus.ERROR],
476
472
  CATEGORIES_SKIPPED,
@@ -482,19 +478,11 @@ class SummaryTotalsDeviceUnderTest(MDReportBase):
482
478
  def generate_rows(self) -> Generator[str, None, None]:
483
479
  """Generate the rows of the summary totals device under test table."""
484
480
  for device, stat in self.results.device_stats.items():
485
- total_tests = (
486
- stat.tests_success_count
487
- + stat.tests_skipped_count
488
- + stat.tests_inconclusive_count
489
- + stat.tests_failure_count
490
- + stat.tests_error_count
491
- + stat.tests_unset_count
492
- )
481
+ total_tests = stat.tests_success_count + stat.tests_skipped_count + stat.tests_failure_count + stat.tests_error_count + stat.tests_unset_count
493
482
  categories_skipped = ", ".join(convert_categories(list(stat.categories_skipped), sort=True))
494
483
  categories_failed = ", ".join(convert_categories(list(stat.categories_failed), sort=True))
495
484
  yield (
496
- f"| **{device}** | {total_tests} | {stat.tests_success_count} | {stat.tests_skipped_count} | {stat.tests_inconclusive_count} "
497
- f"| {stat.tests_failure_count} | {stat.tests_error_count} "
485
+ f"| **{device}** | {total_tests} | {stat.tests_success_count} | {stat.tests_skipped_count} | {stat.tests_failure_count} | {stat.tests_error_count} "
498
486
  f"| {categories_skipped or '-'} | {categories_failed or '-'} |\n"
499
487
  )
500
488
 
@@ -514,7 +502,6 @@ class SummaryTotalsPerCategory(MDReportBase):
514
502
  TOTAL_TESTS,
515
503
  STATUS_MAP[AntaTestStatus.SUCCESS],
516
504
  STATUS_MAP[AntaTestStatus.SKIPPED],
517
- STATUS_MAP[AntaTestStatus.INCONCLUSIVE],
518
505
  STATUS_MAP[AntaTestStatus.FAILURE],
519
506
  STATUS_MAP[AntaTestStatus.ERROR],
520
507
  ]
@@ -525,17 +512,10 @@ class SummaryTotalsPerCategory(MDReportBase):
525
512
  """Generate the rows of the summary totals per category table."""
526
513
  for category, stat in self.results.category_stats.items():
527
514
  converted_category = convert_single_category_cached(category)
528
- total_tests = (
529
- stat.tests_success_count
530
- + stat.tests_skipped_count
531
- + stat.tests_inconclusive_count
532
- + stat.tests_failure_count
533
- + stat.tests_error_count
534
- + stat.tests_unset_count
535
- )
515
+ total_tests = stat.tests_success_count + stat.tests_skipped_count + stat.tests_failure_count + stat.tests_error_count + stat.tests_unset_count
536
516
  yield (
537
- f"| **{converted_category}** | {total_tests} | {stat.tests_success_count} | {stat.tests_skipped_count} | {stat.tests_inconclusive_count} "
538
- f"| {stat.tests_failure_count} | {stat.tests_error_count} |\n"
517
+ f"| **{converted_category}** | {total_tests} | {stat.tests_success_count} | {stat.tests_skipped_count} | {stat.tests_failure_count} "
518
+ f"| {stat.tests_error_count} |\n"
539
519
  )
540
520
 
541
521
  def generate_section(self) -> None:
@@ -619,16 +599,8 @@ class TestResults(MDReportBase):
619
599
  # Format the messages
620
600
  if is_expanded:
621
601
  total = len(result.atomic_results)
622
- inconclusive = len([res for res in result.atomic_results if res.result == AntaTestStatus.INCONCLUSIVE])
623
- failed = len([res for res in result.atomic_results if res.result not in {AntaTestStatus.SUCCESS, AntaTestStatus.INCONCLUSIVE}])
624
- if failed > 0 and inconclusive > 0:
625
- messages_str = f"{failed}/{total}&nbsp;checks&nbsp;failed; {inconclusive}/{total}&nbsp;checks&nbsp;inconclusive"
626
- elif failed > 0:
627
- messages_str = f"{failed}/{total}&nbsp;checks&nbsp;failed"
628
- elif inconclusive > 0:
629
- messages_str = f"{inconclusive}/{total}&nbsp;checks&nbsp;inconclusive"
630
- else:
631
- messages_str = f"All&nbsp;{total}&nbsp;checks&nbsp;passed"
602
+ failed = len([res for res in result.atomic_results if res.result != AntaTestStatus.SUCCESS])
603
+ messages_str = f"{failed}/{total}&nbsp;checks&nbsp;failed" if failed > 0 else f"All&nbsp;{total}&nbsp;checks&nbsp;passed"
632
604
  else:
633
605
  messages_str = self.safe_markdown("<br>".join(result.messages)) or "-"
634
606
 
@@ -43,7 +43,7 @@ class ReportTable:
43
43
  device: str = "Summary per device"
44
44
 
45
45
  @dataclass
46
- class Columns: # pylint: disable=too-many-instance-attributes
46
+ class Columns:
47
47
  """Column names for the table report."""
48
48
 
49
49
  device: str = "Device"
@@ -58,8 +58,6 @@ class ReportTable:
58
58
  number_of_errors: str = "# of errors"
59
59
  failed_devices: str = "List of devices with failed or errored tests"
60
60
  failed_tests: str = "List of failed or errored tests"
61
- # TODO(ANTA 2.0): Group this with the other number_of_* fields when positional compatibility can be broken.
62
- number_of_inconclusive: str = "# of inconclusive"
63
61
 
64
62
  def __init__(self) -> None:
65
63
  """Initialize a ReportTable instance."""
@@ -231,7 +229,6 @@ class ReportTable:
231
229
  - `columns.test`
232
230
  - `columns.number_of_success`
233
231
  - `columns.number_of_skipped`
234
- - `columns.number_of_inconclusive`
235
232
  - `columns.number_of_failure`
236
233
  - `columns.number_of_errors`
237
234
  - `columns.failed_devices`
@@ -251,7 +248,6 @@ class ReportTable:
251
248
  self.columns.test,
252
249
  self.columns.number_of_success,
253
250
  self.columns.number_of_skipped,
254
- self.columns.number_of_inconclusive,
255
251
  self.columns.number_of_failure,
256
252
  self.columns.number_of_errors,
257
253
  self.columns.failed_devices,
@@ -264,7 +260,6 @@ class ReportTable:
264
260
  test,
265
261
  str(stats.devices_success_count),
266
262
  str(stats.devices_skipped_count),
267
- str(stats.devices_inconclusive_count),
268
263
  str(stats.devices_failure_count),
269
264
  str(stats.devices_error_count),
270
265
  ", ".join(stats.devices_failure),
@@ -286,7 +281,6 @@ class ReportTable:
286
281
  - `columns.device`
287
282
  - `columns.number_of_success`
288
283
  - `columns.number_of_skipped`
289
- - `columns.number_of_inconclusive`
290
284
  - `columns.number_of_failure`
291
285
  - `columns.number_of_errors`
292
286
  - `columns.failed_tests`
@@ -306,7 +300,6 @@ class ReportTable:
306
300
  self.columns.device,
307
301
  self.columns.number_of_success,
308
302
  self.columns.number_of_skipped,
309
- self.columns.number_of_inconclusive,
310
303
  self.columns.number_of_failure,
311
304
  self.columns.number_of_errors,
312
305
  self.columns.failed_tests,
@@ -318,7 +311,6 @@ class ReportTable:
318
311
  device,
319
312
  str(stats.tests_success_count),
320
313
  str(stats.tests_skipped_count),
321
- str(stats.tests_inconclusive_count),
322
314
  str(stats.tests_failure_count),
323
315
  str(stats.tests_error_count),
324
316
  ", ".join(stats.tests_failure),
@@ -360,7 +352,7 @@ class ReportTable:
360
352
  """Create a table report with result aggregated per test.
361
353
 
362
354
  Create table with full output:
363
- Test Name | # of success | # of skipped | # of inconclusive | # of failure | # of errors | List of failed or error nodes
355
+ Test Name | # of success | # of skipped | # of failure | # of errors | List of failed or error nodes
364
356
 
365
357
  Warnings
366
358
  --------
@@ -392,7 +384,7 @@ class ReportTable:
392
384
  ) -> Table:
393
385
  """Create a table report with result aggregated per device.
394
386
 
395
- Create table with full output: Device | # of success | # of skipped | # of inconclusive | # of failure | # of errors | List of failed or error test cases
387
+ Create table with full output: Device | # of success | # of skipped | # of failure | # of errors | List of failed or error test cases
396
388
 
397
389
  Warnings
398
390
  --------
@@ -33,21 +33,17 @@ class ResultManager:
33
33
 
34
34
  When adding a test with a status that is not `error`, the overall status is
35
35
  updated according to the following precedence:
36
- `failure` > `inconclusive` > `success` > `skipped`/`unset`.
37
-
38
- | Current Status | Added test Status | Updated Status |
39
- | -------------- | ---------------------------------------- | -------------- |
40
- | unset | Any | Any |
41
- | skipped | unset, skipped | skipped |
42
- | skipped | success | success |
43
- | skipped | inconclusive | inconclusive |
44
- | skipped | failure | failure |
45
- | success | unset, skipped, success | success |
46
- | success | inconclusive | inconclusive |
47
- | success | failure | failure |
48
- | inconclusive | unset, skipped, success, inconclusive | inconclusive |
49
- | inconclusive | failure | failure |
50
- | failure | Any status except error | failure |
36
+ `failure` > `success` > `skipped`/`unset`.
37
+
38
+ | Current Status | Added test Status | Updated Status |
39
+ | -------------- | -------------------------------- | -------------- |
40
+ | unset | Any | Any |
41
+ | skipped | unset, skipped | skipped |
42
+ | skipped | success | success |
43
+ | skipped | failure | failure |
44
+ | success | unset, skipped, success | success |
45
+ | success | failure | failure |
46
+ | failure | Any status except error | failure |
51
47
 
52
48
  If the status of the added test is error, the status is untouched and the
53
49
  `error_status` attribute is set to True.