From 9b3fcc7c992d0f29baa348da0890cbb8c4f4fe68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Wed, 23 Sep 2026 16:55:54 +0200 Subject: [PATCH] Fix stream-specific suffixes in CVE titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró Assisted-by: GPT-5.6 Luna via OpenCode --- ymir/agents/tasks.py | 13 ++++++++-- ymir/agents/tests/unit/test_tasks.py | 39 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/ymir/agents/tasks.py b/ymir/agents/tasks.py index 13a507188..9d51f3814 100644 --- a/ymir/agents/tasks.py +++ b/ymir/agents/tasks.py @@ -853,6 +853,7 @@ async def set_jira_labels( _MAX_GENERATED_TITLE_LENGTH = 80 _MAX_CANONICAL_TITLE_REPLACEMENT_ATTEMPTS = 3 _JIRA_ISSUE_KEY_RE = re.compile(r"\b(?:RHEL|PACKIT)-\d+\b", re.IGNORECASE) +_CVE_STREAM_SUFFIX_RE = re.compile(r"\s+\[rhel-[^\]]+\]\s*$", re.IGNORECASE) _CONDITIONAL_DELETE_LUA = """ if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) @@ -914,6 +915,11 @@ def _validate_canonical_title(title: str, jira_issue: str) -> str: return title +def _normalize_cve_title(title: str) -> str: + """Remove the stream-specific suffix Jira adds to CVE summaries.""" + return _CVE_STREAM_SUFFIX_RE.sub("", title) + + def _validate_generated_title(title: str, jira_issue: str) -> str: """Enforce the stricter title-agent output contract before publication.""" title = _validate_canonical_title(title, jira_issue) @@ -938,6 +944,8 @@ async def _get_cached_canonical_metadata( validator = _validate_canonical_title if is_cve else _validate_generated_title validator(metadata.title, jira_issue) _normalize_jira_updated(metadata.summary_updated) + if is_cve: + metadata = metadata.model_copy(update={"title": _normalize_cve_title(metadata.title)}) return metadata, cached except ValueError as error: logger.warning("Discarding invalid canonical title cache record %s: %s", cache_key, error) @@ -1037,7 +1045,8 @@ async def resolve_canonical_mr_title( The Redis key is stable for the package/family. A matching summary digest reuses the record; only a newer ``summary_updated`` value from its source Jira issue compare-and-swaps a replacement. CVE families use the Jira - summary verbatim; non-CVE families require a validated generated title. + summary with its stream-specific suffix removed; non-CVE families require + a validated generated title. """ jira_summary = _validate_canonical_title(jira_summary, jira_issue) summary_updated = _normalize_jira_updated(summary_updated) @@ -1061,7 +1070,7 @@ async def resolve_canonical_mr_title( logger.info("Kept newer canonical MR title for %s from %s", jira_issue, cache_key) return cached_metadata.title - title = jira_summary if cve_ids else generated_title + title = _normalize_cve_title(jira_summary) if cve_ids else generated_title if title is None: raise ValueError(f"Non-CVE issue {jira_issue} requires a generated title on cache miss") title = ( diff --git a/ymir/agents/tests/unit/test_tasks.py b/ymir/agents/tests/unit/test_tasks.py index 3338c47c8..f4cfd50c3 100644 --- a/ymir/agents/tests/unit/test_tasks.py +++ b/ymir/agents/tests/unit/test_tasks.py @@ -13,6 +13,7 @@ _canonical_mr_title_key, _check_zstream_branch_consistency, _is_newer_summary, + _normalize_cve_title, _normalize_jira_updated, _validate_generated_title, canonical_title_mentions_components, @@ -241,7 +242,7 @@ async def generator(_summary): raise AssertionError("CVE title generation must not run") async def jira_details(*_args, **_kwargs): - return {"fields": {"summary": "CVE-2026-1234 curl: Fix an overflow"}} + return {"fields": {"summary": "CVE-2026-1234 curl: Fix an overflow [rhel-9.9]"}} flexmock(agent_tasks).should_receive("run_tool").replace_with(jira_details).once() title = await resolve_current_canonical_mr_title( @@ -256,6 +257,42 @@ async def jira_details(*_args, **_kwargs): assert title == "CVE-2026-1234 curl: Fix an overflow" +def test_cve_title_removes_stream_suffix(): + assert _normalize_cve_title("CVE-2026-1234 curl: Fix an overflow [rhel-9.9]") == ( + "CVE-2026-1234 curl: Fix an overflow" + ) + assert _normalize_cve_title("CVE-2026-1234 [rhel-9.9] curl: Fix an overflow") == ( + "CVE-2026-1234 [rhel-9.9] curl: Fix an overflow" + ) + + +@pytest.mark.asyncio +async def test_cached_cve_title_removes_existing_stream_suffix(): + redis = CanonicalTitleRedis() + + await resolve_canonical_mr_title( + redis, + package="curl", + jira_issue="RHEL-100", + jira_summary="CVE-2026-1234 curl: Fix an overflow [rhel-9.9]", + cve_id="CVE-2026-1234", + ) + key = next(iter(redis.store)) + metadata = CachedMRMetadata.model_validate_json(redis.store[key]) + metadata.title = "CVE-2026-1234 curl: Fix an overflow [rhel-9.9]" + redis.store[key] = metadata.model_dump_json() + + title = await resolve_canonical_mr_title( + redis, + package="curl", + jira_issue="RHEL-101", + jira_summary="CVE-2026-1234 curl: Fix an overflow [rhel-9.9]", + cve_id="CVE-2026-1234", + ) + + assert title == "CVE-2026-1234 curl: Fix an overflow" + + @pytest.mark.asyncio async def test_current_title_skips_cache_for_multi_family_consolidation(): redis = CanonicalTitleRedis()