Skip to content

canonical(enums): AlertSeverity and ErrorSeverity duplicate the shared Severity enum, and 119 severity literals bypass enums entirely #13597

Closed

Description

@mrveiss

What

autobot_shared/status_enums.py is the canonical enum home — 9 enums, imported in 48 places. Two backend modules nonetheless declare their own severity enum whose member set is an exact subset of the canonical Severity, and 119 call sites skip enums altogether and write bare strings.

Evidence

Canonical — autobot_shared/status_enums.py:73:

class Severity(Enum):
    UNKNOWN = "unknown"
    INFO = "info"
    MINIMAL = "minimal"
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"

Redundant redeclarations — identical member sets, identical base class, no added behaviour:

duplicate file:line members uses / files
AlertSeverity autobot-backend/utils/monitoring_alerts.py:41 LOW, MEDIUM, HIGH, CRITICAL 22 / 2
ErrorSeverity autobot-backend/utils/error_boundaries/types.py:26 LOW, MEDIUM, HIGH, CRITICAL 42 / 6

Plus 119 bare "severity": "<literal>" dict entries across autobot-backend/ and autobot_shared/, which no enum constrains — a typo like "HIGH" or "warn" is accepted silently.

Explicitly NOT part of this — do not merge these

Three same-suffix enums are genuinely different domains. Folding them into Severity would lose meaning:

enum members why it is not a severity
ValidationSeverity (utils/system_validator.py:32) CRITICAL, WARNING, INFO, SUCCESS SUCCESS is an outcome, not a severity — the enum conflates "how bad" with "it passed"
CheckSeverity (api/schemas_analytics.py:1458) BLOCK, WARN, INFO encodes a gate action, not a level
ReviewSeverity (api/schemas_analytics.py:2577) CRITICAL, WARNING, INFO, SUGGESTION review taxonomy; SUGGESTION has no severity equivalent

ValidationSeverity mixing an outcome into a severity scale is a real modelling smell, but it is a separate change with its own blast radius — not this issue.

Why it is safe

AlertSeverity and ErrorSeverity are both plain Enum (not str, Enum), same as canonical Severity, so member identity and comparison semantics are unchanged. Neither module has a single .value read on those members, so there is no serialisation shape to preserve. The swap is import-and-delete-the-declaration, with the alias kept until call sites move — per the standing rule the old name gets wired to the canonical enum, never dropped from under callers.

Scope

  1. Point AlertSeverity / ErrorSeverity at autobot_shared.status_enums.Severity, keeping the old names as aliases so no call site breaks.
  2. Replace the 119 bare string literals with enum members where the value is produced in-process (leave values parsed from external scanner output alone — those are wire data, not domain values).
  3. Consistency inside the canonical file itself: Severity, TaskStatus, Priority, LLMProvider, OperationOutcome, HealthStatus, AgentStatus are plain Enum, while AgentLifecycleStatus and ConnectionStatus are str, Enum. Two serialisation behaviours in one module is a trap for the next caller — pick one and state why in the module docstring.

Discovered

Found while auditing the code-graph and CI work merged 2026-08-04, checking for consolidation opportunities across the modules touched. No behaviour change is being claimed for the merged work — this is pre-existing duplication that the audit surfaced.

Activity

  1. mrveiss commented on Aug 4, 2026

    @mrveiss
    OwnerAuthor

    PR #13606 delivers item 1 — AlertSeverity and ErrorSeverity now alias the canonical Severity.

    Two things worth recording from doing it:

    The canonical module already had the pattern. status_enums.py:70 has WorkflowStatus = TaskStatus, so aliasing rather than deleting is the established convention here, not a new one. And Severity's docstring already claims the #6689 consolidation of 10+ enums — these two were simply missed by it.

    The safety claim in this issue checked out, but only after verifying it. The alias adds three members (UNKNOWN, INFO, MINIMAL) the local enums lacked, which is inert only if nothing depends on the member set. Confirmed: nothing iterates either enum anywhere in autobot-backend/ or autobot_shared/, and all three severity-keyed mappings are .get(…, default) lookups (_max_retries_for_severity at agent_loop/loop.py:1829, _severity_to_tier, and the emoji map in LogNotificationChannel.send_alert), so extra members fall through rather than raising.

    Evidence: ErrorSeverity is Severity → True with identity preserved; all five consuming modules import cleanly; 21 passed on the directly affected tests and 745 passed, 14 skipped across autobot-backend/utils/ + tests/agent_loop/.

    Leaving this issue open — items 2 (119 bare severity literals) and 3 (status_enums.py mixing seven plain Enum with two str, Enum) are not delivered. Item 3 in particular is a decision, not just a change.

  2. mrveiss commented on Aug 10, 2026

    @mrveiss
    OwnerAuthor

    Ran a systematic AST scan for duplicate enums after ce849accfde landed. That commit migrated 2 enums
    across 2 files; the scan finds 15 more that the canonical Severity docstring explicitly claims.

    Posting here rather than filing separately — this is the same consolidation, and #6689 (the parent) is
    closed.

    Method

    Parsed every non-test tracked .py, collected classes inheriting Enum/StrEnum/IntEnum with 3+
    members, then grouped by identical member sets and by duplicate class names.

    Group 1 — same class name, two files, identical members

    Near-certainly true duplicates, not parallel domain concepts. Note the pattern: api/schemas_analytics.py
    re-declares enums that already exist in code_intelligence/
    rather than importing them.

    Enum Members Files
    CheckSeverity BLOCK, INFO, WARN api/schemas_analytics.py · code_intelligence/precommit_analyzer.py
    CheckCategory DEBUG, DOCS, QUALITY, SECURITY, STYLE api/schemas_analytics.py · code_intelligence/precommit_analyzer.py
    ReviewSeverity CRITICAL, INFO, SUGGESTION, WARNING api/schemas_analytics.py · code_intelligence/code_review_engine.py
    ReviewCategory BEST_PRACTICE, BUG_RISK, DOCUMENTATION, MAINTAINABILITY, PERFORMANCE, SECURITY api/schemas_analytics.py · code_intelligence/code_review_engine.py
    LogLevel CRITICAL, DEBUG, ERROR, INFO, WARNING code_intelligence/log_pattern_miner.py · autobot-infrastructure/shared/scripts/logging/log_forwarder.py

    ConfidenceLevel is the worst — four copies, same five members (VERY_LOW…VERY_HIGH):

    api/schemas_analytics.py
    context_aware_decision/types.py
    models/atomic_fact.py
    multimodal_processor/types.py
    

    plus judges/__init__.py::JudgmentConfidence with the identical set under a different name.

    The schema-layer duplication is the same failure shape as #13512's audio allowlist: a second copy written
    by someone who reasonably needed the set and had nothing telling them a canonical one existed. It drifts
    silently, because nothing compares them.

    Group 2 — the severity family the canonical enum was built for

    Nine classes in code_intelligence/, all with exactly CRITICAL, HIGH, MEDIUM, LOW, INFO:

    AntiPatternSeverity    anti_pattern_detection/types.py
    PatternSeverity        cross_language_patterns/models.py
    CloneSeverity          fingerprinting/types.py
    OptimizationPriority   llm_pattern_analysis/types.py
    PatternSeverity        pattern_analysis/types.py
    PerformanceSeverity    performance_analysis/types.py
    OptimizationSeverity   redis_optimizer.py
    SecuritySeverity       security/constants.py
    TestPatternSeverity    test_pattern_analyzer.py
    

    autobot_shared/status_enums.py:73 already exists for this, and its own docstring names the scope:

    Canonical enum for severity, risk, and impact concepts across the codebase. Used in security,
    analytics, code-intelligence, logging, and threat detection. […] consolidates 10+ duplicate
    enums (#6689)

    So code-intelligence is explicitly in scope and none of these nine were migrated. ce849accfde touched
    utils/error_boundaries/types.py and utils/monitoring_alerts.py only.

    Two of these carry the same name (PatternSeverity, in cross_language_patterns and
    pattern_analysis), which is the strongest signal in the group.

    Deliberately not asserted as duplicates

    Capability is defined in four places — a2a/trust_score.py, integrations/capability_registry.py,
    autobot_shared/browser/base.py, plugin_sdk/capabilities.py — with different member sets. Those look
    like genuinely distinct domain concepts that share a noun, and merging them would be wrong. Listed so the
    next scan does not re-raise them.

    Likewise SandboxSecurityLevel and ReviewTicketPriority both being LOW/MEDIUM/HIGH is a coincidence of a
    three-point scale, not a duplication.

    Suggested sequencing

    1. Group 1 first — same name, same members, two files. Mechanical, low-risk, and the schema-layer
      copies are the ones most likely to drift from the domain definitions they mirror.
    2. ConfidenceLevel (4 copies + 1 alias) next, as the highest-count single concept.
    3. Group 2 last and by judgement — nine call sites, and a case can be made that a severity scale
      carrying domain meaning is worth its own name. If they stay separate, aliasing them to Severity (the
      ce849accfde pattern) gets the type safety without renaming anything.
    4. A guard test asserting no new enum duplicates the canonical Severity member set — otherwise this
      regrows, exactly as it did between refactor(types): consolidate 15+ Severity/RiskLevel/Level enums onto constants/status_enums.py:Severity #6689 and now.
  3. mrveiss commented on Aug 10, 2026

    @mrveiss
    OwnerAuthor

    Part of umbrella #13916 — canonical-source duplication

    Wave 2 — canonical source exists and is not used

    Canonical Severity exists at autobot_shared/status_enums.py:73. ce849accfde migrated 2 enums; an AST scan found 15 more, inventory posted on this issue. #6689 already 'finished' this once and it regrew — which is why the umbrella requires a guard test, not just a migration.

    Depends on: Nothing — can run in parallel with #13915.

    Umbrella acceptance criteria that apply to this issue

    1. Lands with a guard test in the shape of repo_tests/audio_extension_allowlist_test.py (tech-debt(transcriber): allowed-audio-extension set duplicated across three modules, one of them a security boundary #13512) — a
      test that fails when a new duplicate of the canonical definition appears. Migration without a guard
      regrows: refactor(types): consolidate 15+ Severity/RiskLevel/Level enums onto constants/status_enums.py:Severity #6689 consolidated 15+ severity enums and 15 exist again today.
    2. Deliberate exceptions are recorded, not omitted — where a copy is kept on purpose, the guard names it
      and says why.
    3. No silent behaviour change — where copies differ, reconciling them alters what the system accepts;
      that is an owner decision recorded before the change.
    4. The canonical source is discoverable from the copy site — the reason each of these was written twice.

    Update #13916 when this issue changes state

    Why these are grouped

    Five findings sharing one cause: a concept has no single authoritative definition, so subsystems each
    keep their own copy and the copies drift. The cost is already paid — a security boundary that drifted
    (#13512/#13615), a week of stale workers (#13539 via #13915), and a severity consolidation that regrew
    to 15 duplicates after being 'finished' (#6689 → #13597).

    #13512 is the worked example on the umbrella: three copies collapsed onto one constant, plus a guard
    test, plus the two deliberate exceptions written down and tracked separately rather than silently
    widening scope.

  4. mrveiss commented on Aug 24, 2026

    @mrveiss
    OwnerAuthor

    Delivered in #14957 (branch issue-13845-enum-union, head 17b58390). Member sets, deliberate-fork findings and the per-fork union tables are in the PR body. Not merged — CI in flight.

  5. mrveiss commented on Aug 24, 2026

    @mrveiss
    OwnerAuthor

    Partially delivered by PR #14957 (342eeb05e) — this issue stays OPEN.

    The enum half is done and is now asserted to stay done. AlertSeverity/ErrorSeverity were already collapsed onto the shared Severity on base; that state is now pinned by an AST guard rather than left to convention, so a re-fork fails by name.

    The literal half is not done, and the count in this issue is wrong. Measured against the tree: 169 severity literals across 43 files, not 119. More usefully, 43 of them are already out of vocabulary — warning ×25, HIGH ×9, CRITICAL ×3, MEDIUM ×3, plus error, degraded and Extreme. So a third of the population would not survive contact with the enum even if swept mechanically.

    Sweeping 169 across 43 files was judged too large to land safely alongside the enum union, so PR #14957 ships a ratchet pinned at 169 — the count can only fall, and growth past it goes red. Verified down-only by construction, with floor assertions guarding against a silently-collapsed scan reporting a false-clean zero.

    The remainder is filed as #14956 with the full distribution. This issue stays open until the literal half is drained; #14956 carries the work.

  6. github-actions commented on Aug 24, 2026

    @github-actions
    Contributor

    PR #14957 (merged to Dev_new_gui) references this issue with a close keyword.

    fix(enums): collapse four divergent enum forks to the union of their members (#13845, #13846, #13597, #13578)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  7. mrveiss commented on Aug 24, 2026

    @mrveiss
    OwnerAuthor

    Half of this landed in PR #14990 (squash e2daa78685989c92b5ec04d0460e67e947e0240b, verified in Dev_new_gui) as Refs, not Closes.

    Done:

    Note the count of 119 in this issue's title predates the growth — the measured population at the time of the sweep was 169.

    Still open here:

    Discovered while doing the above, tracked separately:

  8. mrveiss commented on Sep 2, 2026

    @mrveiss
    OwnerAuthor

    Re-measured all three scope items against Dev_new_gui at 2363ad3aa9. Scope items 1 and 3
    are done; item 2 is the only one left, and its count is off by an order of magnitude in the
    opposite direction to what the title says.
    PR #15508 covers the guard half.

    Scope item 1 — the enum forks: DONE, and now guarded three ways

    AlertSeverity and ErrorSeverity are aliases (#14957, guarded in
    repo_tests/enum_union_guard_test.py). CausalSeverity, filed as a fourth fork on #14988, is
    also already an alias — services/causal_inference_engine.py:36 — but was unguarded until
    #15508.

    The union carries every member of every fork; nothing was dropped to reach it:

    member value contributed by
    UNKNOWN INFO MINIMAL canonical (#6689)
    LOW MEDIUM HIGH CRITICAL AlertSeverity, ErrorSeverity, CausalSeverity
    WARNING warning CausalSeverity, Prometheus label (#14956)
    DEGRADED degraded CausalSeverity, causal API response (#14956)
    ERROR error capability audit grade (#14956)

    Scope item 3 — plain Enum vs (str, Enum): DONE

    The rule is stated in the status_enums module docstring and each (str, Enum) carries a
    per-class reason. This was recorded as parked and as blocking #14988; it is neither.

    Scope item 2 — the literals: MEASURED, and the title's 119 is not the number

    shape guarded by measured today
    "severity": "<literal>" dict entry enum_union_guard_test.py, floor 20 / ceiling 25 25 across 6 files
    severity= keyword / assignment nothing, until #15508 73 across 29 files
    == "<literal>" comparison nothing, until #15508 121 across 40 files

    The title's 119 grew to 169, of which #14956 converted 144 — but only within the one shape the
    matcher can see. The matcher requires a quoted key followed by a colon:

    _SEVERITY_LITERAL = re.compile(r"""["']severity["']\s*:\s*["'][A-Za-z_]+["']""")
    

    so 194 literals in the other two shapes were never in scope of the number this issue has been
    tracking. #15508 ratchets both.

    A decision this issue should make, not a PR

    Nine enums in code_intelligence/ declare an identical member set — INFO, LOW, MEDIUM, HIGH, CRITICAL, a strict subset of canonical Severity:

    AntiPatternSeverity   anti_pattern_detection/types.py:18
    PatternSeverity       cross_language_patterns/models.py:53
    CloneSeverity         fingerprinting/types.py:42
    OptimizationPriority  llm_pattern_analysis/types.py:102
    PatternSeverity       pattern_analysis/types.py:32
    PerformanceSeverity   performance_analysis/types.py:17
    OptimizationSeverity  redis_optimizer.py:42
    SecuritySeverity      security/constants.py:32
    TestPatternSeverity   testing_pattern_analyzer.py:58
    

    Two of them share the name PatternSeverity in different packages, which is the strongest
    duplication signal in the set. But the two issues covering this contradict each other: the
    inventory comment here says "Group 2 last and by judgement", while #14881 rules the generic
    ladders out as "not consolidation candidates".

    Recommendation: alias all nine to canonical Severity, keeping every name, exactly as
    AlertSeverity / ErrorSeverity / CausalSeverity were done. That gets type safety with no
    rename and no call-site change. The one thing to verify per module first is that nothing
    iterates the enum or uses it as a Pydantic field, because the alias widens the accepted set
    from five members to ten.

    Not doing it inside a PR unasked — it is nine modules and a widened accepted vocabulary in
    each, which is the kind of change that should be decided on the issue rather than discovered
    in a diff. Labelling needs-decision.

    State

  9. added
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Sep 2, 2026
  10. mrveiss commented on Sep 6, 2026

    @mrveiss
    OwnerAuthor

    Closed with evidence. Verified against merged origin/Dev_new_gui at 017f7d41da.

    The duplication this issue describes no longer exists. Neither AlertSeverity nor
    ErrorSeverity is declared anywhere in the tree — a walk of every tracked .py file finds no
    class AlertSeverity and no class ErrorSeverity. The only surviving mention of either name is
    repo_tests/enum_union_guard_test.py, which is a guard against the duplication returning, not an
    instance of it.

    autobot_shared/status_enums.py remains the canonical home and still declares Severity.

    No decision was needed: the canonicalisation work that followed this filing resolved it. The
    needs-decision label outlived the question rather than the question outliving an answer — which
    is why this closes on evidence rather than on a choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions