Repository navigation
canonical(enums): AlertSeverity and ErrorSeverity duplicate the shared Severity enum, and 119 severity literals bypass enums entirely #13597
Description
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
- Point
AlertSeverity/ErrorSeverityatautobot_shared.status_enums.Severity, keeping the old names as aliases so no call site breaks. - 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).
- Consistency inside the canonical file itself:
Severity,TaskStatus,Priority,LLMProvider,OperationOutcome,HealthStatus,AgentStatusare plainEnum, whileAgentLifecycleStatusandConnectionStatusarestr, 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
mrveiss commentedon Aug 4, 2026
PR #13606 delivers item 1 —
AlertSeverityandErrorSeveritynow alias the canonicalSeverity.Two things worth recording from doing it:
The canonical module already had the pattern.
status_enums.py:70hasWorkflowStatus = TaskStatus, so aliasing rather than deleting is the established convention here, not a new one. AndSeverity'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 inautobot-backend/orautobot_shared/, and all three severity-keyed mappings are.get(…, default)lookups (_max_retries_for_severityatagent_loop/loop.py:1829,_severity_to_tier, and the emoji map inLogNotificationChannel.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 passedon the directly affected tests and745 passed, 14 skippedacrossautobot-backend/utils/+tests/agent_loop/.Leaving this issue open — items 2 (119 bare severity literals) and 3 (
status_enums.pymixing seven plainEnumwith twostr, Enum) are not delivered. Item 3 in particular is a decision, not just a change.refactor(enums): alias AlertSeverity and ErrorSeverity to the canonic…
mrveiss commentedon Aug 10, 2026
Ran a systematic AST scan for duplicate enums after
ce849accfdelanded. That commit migrated 2 enumsacross 2 files; the scan finds 15 more that the canonical
Severitydocstring 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 inheritingEnum/StrEnum/IntEnumwith 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.pyre-declares enums that already exist in
code_intelligence/rather than importing them.CheckSeverityapi/schemas_analytics.py·code_intelligence/precommit_analyzer.pyCheckCategoryapi/schemas_analytics.py·code_intelligence/precommit_analyzer.pyReviewSeverityapi/schemas_analytics.py·code_intelligence/code_review_engine.pyReviewCategoryapi/schemas_analytics.py·code_intelligence/code_review_engine.pyLogLevelcode_intelligence/log_pattern_miner.py·autobot-infrastructure/shared/scripts/logging/log_forwarder.pyConfidenceLevelis the worst — four copies, same five members (VERY_LOW…VERY_HIGH):plus
judges/__init__.py::JudgmentConfidencewith 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 exactlyCRITICAL, HIGH, MEDIUM, LOW, INFO:autobot_shared/status_enums.py:73already exists for this, and its own docstring names the scope:So code-intelligence is explicitly in scope and none of these nine were migrated.
ce849accfdetouchedutils/error_boundaries/types.pyandutils/monitoring_alerts.pyonly.Two of these carry the same name (
PatternSeverity, incross_language_patternsandpattern_analysis), which is the strongest signal in the group.Deliberately not asserted as duplicates
Capabilityis 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 looklike 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
SandboxSecurityLevelandReviewTicketPriorityboth being LOW/MEDIUM/HIGH is a coincidence of athree-point scale, not a duplication.
Suggested sequencing
copies are the ones most likely to drift from the domain definitions they mirror.
ConfidenceLevel(4 copies + 1 alias) next, as the highest-count single concept.carrying domain meaning is worth its own name. If they stay separate, aliasing them to
Severity(thece849accfdepattern) gets the type safety without renaming anything.Severitymember set — otherwise thisregrows, exactly as it did between refactor(types): consolidate 15+ Severity/RiskLevel/Level enums onto constants/status_enums.py:Severity #6689 and now.
mrveiss commentedon Aug 10, 2026
Part of umbrella #13916 — canonical-source duplication
Wave 2 — canonical source exists and is not used
Canonical
Severityexists atautobot_shared/status_enums.py:73.ce849accfdemigrated 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
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) — atest 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.
and says why.
that is an owner decision recorded before the change.
Update #13916 when this issue changes state
leaves the ordering wrong for everything downstream.
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.
mrveiss commentedon Aug 24, 2026
Delivered in #14957 (branch
issue-13845-enum-union, head17b58390). Member sets, deliberate-fork findings and the per-fork union tables are in the PR body. Not merged — CI in flight.fix(enums): collapse four divergent enum forks to the union of their …
mrveiss commentedon Aug 24, 2026
Partially delivered by PR #14957 (
342eeb05e) — this issue stays OPEN.The enum half is done and is now asserted to stay done.
AlertSeverity/ErrorSeveritywere already collapsed onto the sharedSeverityon 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, pluserror,degradedandExtreme. 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.
github-actions commentedon Aug 24, 2026
PR #14957 (merged to
Dev_new_gui) references this issue with a close keyword.If this issue is fully resolved, close it manually. If work remains, no action is needed.
fix(enums): converge severity literals on the canonical enum and add …
mrveiss commentedon Aug 24, 2026
Half of this landed in PR #14990 (squash
e2daa78685989c92b5ec04d0460e67e947e0240b, verified inDev_new_gui) as Refs, not Closes.Done:
AlertSeverity/ErrorSeverityare aliases of canonicalSeverity, landed earlier in fix(enums): collapse four divergent enum forks to the union of their members (#13845, #13846, #13597, #13578) #14957 and guarded byrepo_tests/enum_union_guard_test.py.SEVERITY_LITERAL_FLOOR = 20/SEVERITY_LITERAL_CEILING = 25with a documentedDELIBERATE_SEVERITY_LITERALSallowlist carrying two qualifying reasons (external boundary, documentation prose). Closed as canonical(enums): 169 bare severity literals remain, 43 of them outside the canonical vocabulary #14956.warning,degraded,error.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:
Enumvs(str, Enum)rule, which is parked and blocks canonical(enums): CausalSeverity is a fourth severity fork, and 219 severity literals sit outside the ratchet's matcher #14988'sCausalSeveritycollapse.AgentLifecycleStatus/ConnectionStatusreconciliation.Discovered while doing the above, tracked separately:
CausalSeverityis a fourth severity fork, plus 219 literals in two shapes the ratchet's matcher cannot see (severity=97,== "…"122, across 55 files), 20 of them outside the vocabulary.mrveiss commentedon Sep 2, 2026
Re-measured all three scope items against
Dev_new_guiat2363ad3aa9. Scope items 1 and 3are 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
AlertSeverityandErrorSeverityare aliases (#14957, guarded inrepo_tests/enum_union_guard_test.py).CausalSeverity, filed as a fourth fork on #14988, isalso 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:
UNKNOWNINFOMINIMALLOWMEDIUMHIGHCRITICALAlertSeverity,ErrorSeverity,CausalSeverityWARNINGwarningCausalSeverity, Prometheus label (#14956)DEGRADEDdegradedCausalSeverity, causal API response (#14956)ERRORerrorScope item 3 — plain
Enumvs(str, Enum): DONEThe rule is stated in the
status_enumsmodule docstring and each(str, Enum)carries aper-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
"severity": "<literal>"dict entryenum_union_guard_test.py, floor 20 / ceiling 25severity=keyword / assignment== "<literal>"comparisonThe 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:
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 canonicalSeverity:Two of them share the name
PatternSeverityin different packages, which is the strongestduplication 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 asAlertSeverity/ErrorSeverity/CausalSeveritywere done. That gets type safety with norename 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
(str, Enum)rule stated, with per-class reasonscanonical(enums): CausalSeverity is a fourth severity fork, and 219 severity literals sit outside the ratchet's matcher #14988's item 4
code_intelligence/ladders — decision abovetest(enums): ratchet the two severity-literal shapes the #13597 guard…
mrveiss commentedon Sep 6, 2026
Closed with evidence. Verified against merged
origin/Dev_new_guiat017f7d41da.The duplication this issue describes no longer exists. Neither
AlertSeveritynorErrorSeverityis declared anywhere in the tree — a walk of every tracked.pyfile finds noclass AlertSeverityand noclass ErrorSeverity. The only surviving mention of either name isrepo_tests/enum_union_guard_test.py, which is a guard against the duplication returning, not aninstance of it.
autobot_shared/status_enums.pyremains the canonical home and still declaresSeverity.No decision was needed: the canonicalisation work that followed this filing resolved it. The
needs-decisionlabel outlived the question rather than the question outliving an answer — whichis why this closes on evidence rather than on a choice.