ariadne/tests/test_hermes_incident_attribution.py

137 lines
5.0 KiB
Python
Raw Normal View History

"""Tests that an issue attributes its finding to whoever actually made it."""
from __future__ import annotations
from types import SimpleNamespace
from ariadne.services import hermes_incident_body as body
from ariadne.services import hermes_incident_issue as module
feat(hermes): one open proposal per rule, and link every issue to its run One SonarQube rule is usually one root cause spread across many files. S2208 appears in three Ariadne modules and the cognitive-complexity rule in dozens, and a sweep with no memory of what it already proposed would open a near-identical pull request for every instance. Thirty of those get read as none, which costs more than proposing nothing. The sweep now skips any rule that already has an open proposal for that project. The rules under review are read back from the open pull requests' own titles rather than from a stored index: the pull requests are the thing that actually exists, an index could disagree with them, and disagreeing is the one failure mode that matters here. Once the open one is dealt with, the next instance of that rule becomes eligible again. This is not the root-cause collapse - it does not make one pull request fix every instance of a rule, it just stops proposing the same rule repeatedly. The collapse needs multi-file patch sets, which the frozen patch contract cannot express yet. Fails open like every other duplicate check here: an unreadable list yields no known rules, so a lookup failure costs one extra proposal rather than silently dropping a whole rule. Issues now link their run id into the Hermes console, matching what pull requests already do. Both artifacts claim a model made the call; both should let a reader open the page where that call is visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 00:39:05 -03:00
_BASE = {"incident_id": "ariadne/408", "job": "ariadne", "build_number": 408}
def _diagnosis(**overrides):
"""A completed diagnosis, as maybe_file_issue receives it."""
diagnosis = {
"bundle": {"jenkins": {"url": "https://ci.example/job/ariadne/408/"}},
"outcome": None,
"authorize_reason": "human_required",
"run_id": "run_a5af87af",
}
diagnosis.update(overrides)
return diagnosis
def test_body_does_not_claim_a_diagnosis_that_never_happened() -> None:
"""A hung build is escalated without any model call.
Saying it came "from a Hermes Agent diagnosis" would misattribute a direct
observation to a model conclusion.
"""
context = module.issue_context(
{"incident_id": "metis/272", "job": "metis", "build_number": 272},
{
"bundle": {
"jenkins": {
"url": "https://ci.example/job/metis/272/",
"console_failures": [],
"console_tail": "Build metis #272 has been running for 75.0 minutes.",
}
},
"outcome": None,
"authorize_reason": "hung_build",
"run_id": None,
},
)
rendered = body.issue_body(context)
assert "No Hermes diagnosis was requested" in rendered
assert "from a Hermes Agent diagnosis" not in rendered
assert "has been running for 75.0 minutes" in rendered
def test_body_keeps_the_hermes_attribution_when_a_run_happened() -> None:
context = module.issue_context(
{"incident_id": "metis/272", "job": "metis", "build_number": 272},
{"bundle": {}, "outcome": None, "authorize_reason": "x", "run_id": "run_abc"},
)
rendered = body.issue_body(context)
assert "run `run_abc`" in rendered
assert "No Hermes diagnosis was requested" not in rendered
def test_observation_is_omitted_when_a_diagnosis_cited_facts() -> None:
"""Console text is a fallback, not a duplicate of a real diagnosis."""
jenkins = {"console_failures": [{"text": "boom"}], "console_tail": "tail text"}
assert module._observation(jenkins) == ""
assert module._observation(None) == ""
assert module._observation({"console_tail": " only tail "}) == "only tail"
def test_headline_credits_ariadne_when_no_model_ran() -> None:
"""The summary line is read first; it must not credit a model either."""
context = module.issue_context(
{"incident_id": "lesavka/584", "job": "lesavka", "build_number": 584},
{"bundle": {}, "outcome": None, "authorize_reason": "hung_build",
"run_id": None, "classification": "build_exceeded_time_cap"},
)
rendered = body.issue_body(context)
assert rendered.startswith("Ariadne recorded incident `lesavka/584` as **build_exceeded_time_cap**.")
assert "Hermes auto-triage classified" not in rendered
def test_headline_keeps_hermes_wording_and_confidence_for_a_real_run() -> None:
decision = SimpleNamespace(
classification="pytest_test_failure", confidence=0.99,
first_failed_gate="tests", reason="r", facts=[], inferences=[],
)
context = module.issue_context(
{"incident_id": "ariadne/409", "job": "ariadne", "build_number": 409},
{"bundle": {}, "outcome": SimpleNamespace(decision=decision),
"authorize_reason": "human_required", "run_id": "run_abc"},
)
rendered = body.issue_body(context)
assert "Hermes auto-triage classified incident `ariadne/409`" in rendered
assert "confidence 0.99" in rendered
def test_explicit_classification_is_used_only_without_a_decision() -> None:
"""A real diagnosis must never be overridden by a fallback label."""
decision = SimpleNamespace(
classification="pytest_test_failure", confidence=0.9,
first_failed_gate="t", reason="r", facts=[], inferences=[],
)
ctx = module.issue_context(
{"incident_id": "a/1", "job": "a", "build_number": 1},
{"bundle": {}, "outcome": SimpleNamespace(decision=decision),
"classification": "build_exceeded_time_cap", "run_id": "run_x"},
)
assert ctx["classification"] == "pytest_test_failure"
feat(hermes): one open proposal per rule, and link every issue to its run One SonarQube rule is usually one root cause spread across many files. S2208 appears in three Ariadne modules and the cognitive-complexity rule in dozens, and a sweep with no memory of what it already proposed would open a near-identical pull request for every instance. Thirty of those get read as none, which costs more than proposing nothing. The sweep now skips any rule that already has an open proposal for that project. The rules under review are read back from the open pull requests' own titles rather than from a stored index: the pull requests are the thing that actually exists, an index could disagree with them, and disagreeing is the one failure mode that matters here. Once the open one is dealt with, the next instance of that rule becomes eligible again. This is not the root-cause collapse - it does not make one pull request fix every instance of a rule, it just stops proposing the same rule repeatedly. The collapse needs multi-file patch sets, which the frozen patch contract cannot express yet. Fails open like every other duplicate check here: an unreadable list yields no known rules, so a lookup failure costs one extra proposal rather than silently dropping a whole rule. Issues now link their run id into the Hermes console, matching what pull requests already do. Both artifacts claim a model made the call; both should let a reader open the page where that call is visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 00:39:05 -03:00
def test_the_issue_footer_links_to_the_hermes_run() -> None:
"""A bare id is something to copy; a link is a page to open."""
context = module.issue_context(_BASE, _diagnosis(), "https://agent.bstein.dev")
rendered = body.issue_body(context)
assert context["run_url"].startswith("https://agent.bstein.dev/chat?resume=")
assert f"run [{context['run_id']}]({context['run_url']})" in rendered
def test_the_footer_falls_back_to_a_bare_id_without_a_console() -> None:
context = module.issue_context(_BASE, _diagnosis())
rendered = body.issue_body(context)
assert context["run_url"] == ""
assert f"run `{context['run_id']}`" in rendered