Skip to content

test(enums): ratchet the two severity-literal shapes the #13597 guard cannot see (#14988, #13597) - #15508

Merged
mrveiss merged 5 commits into
Dev_new_guifrom
issue-13597-severity-union
Sep 2, 2026
Merged

mrveiss merged 5 commits into
Dev_new_guifrom
issue-13597-severity-union

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Thinking Path

The three issues in this cluster describe a severity enum fork set. Measured against
Dev_new_gui first, before touching anything, because the counts in this repo drift in both
directions — and here they had drifted in both, plus one whole claim had gone stale.

Measured vs claimed

claim issue claimed measured verdict
AlertSeverity / ErrorSeverity are forks #13597 2 forks 0 — both are aliases of canonical Severity, guarded already landed
CausalSeverity is a fourth fork declaring (str, Enum) #14988 1 fork 0 — already import Severity as CausalSeverity, but unguarded STALE
plain Enum vs (str, Enum) rule is unstated and blocks the collapse #14988 blocking rule is stated in the module docstring, with a per-class reason on each (str, Enum) STALE
bare severity literals, dict shape #13597 119 25 across 6 files, already ratcheted at floor 20 / ceiling 25 landed
severity literals outside the ratchet's matcher #14988 219 across 55 files 194 across 53 files (keyword 73, comparison 121) overcounted
of those, values outside the canonical vocabulary #14988 20 20 — none 10, moderate 7, missing 2, forbidden 1 exact
enums whose name ends in Severity — not claimed 20 declared, of which 9 in code_intelligence/ share one member set new measurement

What the ratchet's matcher actually matches, quoted from repo_tests/enum_union_guard_test.py:

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

It requires a quoted key followed by a colon. So it sees a dict entry and nothing else —
every literal written as a keyword argument, an attribute assignment or an equality
comparison is structurally invisible to it. That is why 194 literals sit outside a guard that
reports 25.

The union, enumerated before proposing anything. Canonical Severity already carries the
union of every collapsed fork:

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

Nothing was dropped to reach it: all ten rungs are present, and the three names the forks used
(AlertSeverity, ErrorSeverity, CausalSeverity) still resolve, so no call site broke.

The per-value decision #14988 asked for, answered with evidence. All four out-of-vocabulary
words are a different enum's vocabulary in a field named severity — a mis-typed field, not a
missing rung. None of them gets folded in:

  • forbidden — a dangerous-command grade; that ladder is CommandRisk, which already has
    FORBIDDEN. The field is annotated str with the three words in a trailing comment.
  • none / moderate — the delta engine's metric-change classification. none means no
    finding at all
    , which no severity ladder can express.
  • missing — the env drift detector records a drift kind alongside type_mismatch and
    unknown, not how bad the drift is.

That answer is the load-bearing part: the cheapest way to make an out-of-vocabulary count go
to zero is to widen Severity, which would make the guard read clean while making the
canonical enum mean less. The guard fails if anyone takes that shortcut.

What Changed

repo_tests/severity_literal_shape_guard_test.py (new, 428 lines) — the #14988 guard.

  • Ratchets both unguarded shapes, floor before ceiling: keyword 68-73, comparison 116-121,
    and the file spread 48-53. Down only; a conversion run lowers both bounds in one commit.
  • Ratchets the out-of-vocabulary population 15-20 and pins the four words by name, so a
    new bad word fails even while the total stays under the ceiling.
  • Fails if canonical Severity absorbs any of the four, with the reason for each in the
    failure message.
  • Pins the two things canonical(enums): CausalSeverity is a fourth severity fork, and 219 severity literals sit outside the ratchet's matcher #14988 reports as undone but that had already landed unguarded: that
    CausalSeverity is still an alias binding (read by AST, so a re-declaration of the name
    fails), and that the collapse stayed safe — the report serialises severity.value and the
    diagnostics endpoint returns to_dict(), which is the entire argument for dropping the
    (str, Enum) base.
  • Two population floors run before every count, and each matcher is asserted against a real
    tracked file as well as against synthetic strings, so a matcher that has drifted away from
    what the producers write fails instead of reading clean.
  • Shells out to git with env=scrubbed_git_env() and drops .worktrees/ by repo-relative
    path.

autobot_shared/status_enums.py — the Severity docstring now names the three vocabularies
that are not severity and points at the guard. This is the discoverability half: the reason
each copy got written was that nothing at the canonical source said what belongs there.

Deliberately not in this PR, with reasons rather than a bundle:

Verification

New guard, clean tree: 24 passed.

Existing enum guard unaffected: repo_tests/enum_union_guard_test.py + the new file together
91 passed. File-size ratchet 31 passed (new file 428 lines, no entry added to either half
of the checked pair).

Mutation proof — every assertion made to fail, then reverted.

  1. Scanned roots repointed at a directory that does not exist. Seven tests fail; an empty
    sweep does not read as clean:
    AssertionError: only 0 tracked files under ('no-such-root/',) - the roots moved and every ratchet below is counting nothing
    AssertionError: #14988: only 0 keyword-shaped severity literals found (floor 68).
    AssertionError: #14988: only 0 comparison-shaped severity literals found (floor 116).
    AssertionError: #14988: only 0 files carry an unguarded severity literal (floor 48) - the sweep stopped reaching the tree.
    AssertionError: #14988: only 0 out-of-vocabulary severity literals (floor 15).
    AssertionError: #14988: the out-of-vocabulary scan found nothing - it is broken
    AssertionError: #14988: the unguarded shapes no longer outnumber the guarded one
    
  2. A new tracked module adding twelve literals, one carrying an unknown word. Five ratchets
    fire:
    AssertionError: #14988: 79 keyword-shaped severity literals, over the ceiling of 73.
    AssertionError: #14988: 127 comparison-shaped severity literals, over the ceiling of 121.
    AssertionError: #14988: 54 files now carry an unguarded severity literal, over the ceiling of 53. The defect spread to a new module.
    AssertionError: #14988: 26 severity literals carry a value no Severity member has, over the ceiling of 20: {'none': 10, 'moderate': 7, 'bogus': 6, 'missing': 2, 'forbidden': 1}
    AssertionError: #14988: new severity values outside the canonical vocabulary: ['bogus'].
    
  3. Severity given a moderate rung — the shortcut fix:
    AssertionError: #14988: only 13 out-of-vocabulary severity literals (floor 15).
    AssertionError: #14988: Severity gained ['moderate'] - those are CommandRisk / delta-classification / drift-kind vocabularies, not severity rungs. Type the field correctly instead.
    
  4. CausalSeverity re-declared as its own (str, Enum):
    AssertionError: #14988: CausalSeverity was re-declared as its own enum in autobot-backend/services/causal_inference_engine.py
    
  5. The causal report stops serialising through .value:
    AssertionError: #14988: ...causal_inference_engine.py no longer serialises severity via .value - a plain Enum does not stringify itself, so the wire value changed
    
  6. Witness files repointed at a module carrying neither shape — the matcher-stopped-matching
    trap:
    AssertionError: #14988: no keyword-shaped severity literal left in autobot_shared/__init__.py - if it was genuinely converted, repoint the witness; do not delete it
    

Every probe was removed from disk and from the index; git status is clean and git diff on
the two product files touched during mutation is empty.

Pre-existing failure found while verifying, not caused here. The full repo_tests/ run is
1 failed, 1230 passed, 17 skipped, 2 xfailed. The failure is
sdk_response_model_contract_test.py::test_every_sdk_response_model_is_paired_or_explicitly_unpaired,
which passes in isolation and fails only after sdk_request_signature_params_test.py has run
and left five parameterised generic response aliases in the model registry. Reproduced with
this PR's file excluded from the run, so it is not this change. Filed separately.

Not verified here: nothing in this PR imports backend application code, so CI is the first
place the declared dependency set is exercised. The local run reports 91 of 206 declared
versions unsatisfied.

Refs #13597, Refs #14988

Model Used

Claude Opus 5 (1M context)

Auto-regenerated (py3.14) to match the backend and/or SLM backend
schema so the required verify-generated-types gate(s) pass.
Triggered by auto-fix-generated-types.yml.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

@mrveiss
mrveiss merged commit 0937e15 into Dev_new_gui Sep 2, 2026
75 checks passed
@mrveiss
mrveiss deleted the issue-13597-severity-union branch September 2, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant