From 85d9e89a6dc3a01a517faa3b15d41121234944e7 Mon Sep 17 00:00:00 2001 From: Filip Janus Date: Wed, 26 Aug 2026 13:34:26 +0200 Subject: [PATCH] Fix modular downstream_component parsing for branch mapping The Jira Downstream Component Name field (customfield_10669) stores the full modular string "module:stream/package" (e.g. "postgresql:16/postgis") for modular issues, but is_modular() and parse_module_stream() expect just the package name. Extract the package part so modular issues get the correct stream-specific branch (e.g. stream-postgresql-16-rhel-9.8.0) instead of falling through to the non-modular rhel-9.8.0. Co-authored-by: Cursor --- ymir/agents/tests/unit/test_triage_agent.py | 11 ++++++- ymir/agents/triage_agent.py | 11 +++++-- ymir/common/tests/unit/test_version_utils.py | 32 ++++++++++++++++++++ ymir/common/version_utils.py | 12 ++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/ymir/agents/tests/unit/test_triage_agent.py b/ymir/agents/tests/unit/test_triage_agent.py index 1efa1d355..ade32399f 100644 --- a/ymir/agents/tests/unit/test_triage_agent.py +++ b/ymir/agents/tests/unit/test_triage_agent.py @@ -22,7 +22,7 @@ TriageEligibility, TriageOutputSchema, ) -from ymir.common.version_utils import is_modular, parse_module_stream +from ymir.common.version_utils import extract_downstream_package, is_modular, parse_module_stream @pytest.mark.parametrize( @@ -316,6 +316,15 @@ def test_map_version_to_module_branch_invalid_version(): assert branch is None +def test_map_version_to_module_branch_extracts_package_from_raw_field(): + """customfield_10669 stores module:stream/package; mapping needs the package.""" + raw = "postgresql:16/postgis" + summary = "postgresql:16/postgis: PostGIS: vuln" + assert _map_version_to_module_branch("rhel-9.8", summary, raw) is None + package = extract_downstream_package(raw) + assert _map_version_to_module_branch("rhel-9.8", summary, package) == "stream-postgresql-16-rhel-9.8.0" + + # --- Modular target branch + namespace selection --- diff --git a/ymir/agents/triage_agent.py b/ymir/agents/triage_agent.py index aede715b7..87f7688cd 100644 --- a/ymir/agents/triage_agent.py +++ b/ymir/agents/triage_agent.py @@ -75,6 +75,7 @@ ) from ymir.common.version_utils import ( construct_internal_branch_name, + extract_downstream_package, is_modular, is_older_zstream, normalize_fix_version, @@ -410,7 +411,10 @@ class TriageState(BaseModel): ) downstream_component: str | None = Field( default=None, - description="Jira Downstream Component Name (customfield_10669), used for modular detection.", + description=( + "Package name from Jira Downstream Component Name (customfield_10669). " + "Modular values are reduced from 'module:stream/package' to the package." + ), ) jira_summary: str | None = Field( default=None, @@ -645,7 +649,10 @@ async def run_triage_analysis(state): input_data = InputSchema(issue=state.jira_issue) state.jira_summary = jira_details.get("fields", {}).get("summary") - state.downstream_component = jira_details.get("fields", {}).get(DOWNSTREAM_COMPONENT_CUSTOM_FIELD) + raw_component = jira_details.get("fields", {}).get(DOWNSTREAM_COMPONENT_CUSTOM_FIELD) + # Modular issues store "module:stream/package" (e.g. "postgresql:16/postgis"); + # extract the package so is_modular() / parse_module_stream() match the summary. + state.downstream_component = extract_downstream_package(raw_component) response = await triage_agent.run( await render_prompt( input_data, diff --git a/ymir/common/tests/unit/test_version_utils.py b/ymir/common/tests/unit/test_version_utils.py index f62df1eb3..85e064765 100644 --- a/ymir/common/tests/unit/test_version_utils.py +++ b/ymir/common/tests/unit/test_version_utils.py @@ -2,7 +2,9 @@ from flexmock import flexmock from ymir.common.version_utils import ( + extract_downstream_package, get_maintenance_rhel_branch, + is_modular, is_older_zstream, parse_branch_name, parse_module_stream, @@ -199,3 +201,33 @@ async def mock_load_rhel_config(): ) def test_parse_module_stream(summary, component, expected): assert parse_module_stream(summary, component) == expected + + +@pytest.mark.parametrize( + "raw, expected", + [ + ("postgresql:16/postgis", "postgis"), + ("nodejs:18/nodejs", "nodejs"), + ("perl:5.32/perl-IO-Socket-SSL", "perl-IO-Socket-SSL"), + ("libtiff", "libtiff"), + ("regular-component", "regular-component"), + (None, None), + ("", None), + ], +) +def test_extract_downstream_package(raw, expected): + assert extract_downstream_package(raw) == expected + + +def test_is_modular_requires_package_not_full_modular_string(): + summary = "postgresql:16/postgis: PostGIS: vuln" + raw = "postgresql:16/postgis" + assert is_modular(summary, raw) is False + assert is_modular(summary, extract_downstream_package(raw)) is True + + +def test_parse_module_stream_requires_package_not_full_modular_string(): + summary = "postgresql:16/postgis: PostGIS: vuln" + raw = "postgresql:16/postgis" + assert parse_module_stream(summary, raw) is None + assert parse_module_stream(summary, extract_downstream_package(raw)) == ("postgresql", "16") diff --git a/ymir/common/version_utils.py b/ymir/common/version_utils.py index 02a035253..d4392e4d3 100644 --- a/ymir/common/version_utils.py +++ b/ymir/common/version_utils.py @@ -349,6 +349,18 @@ async def is_older_zstream( MODULAR_SUMMARY_PREFIX = r"^(?:\S+\s+)*([\w.+-]+):([^/\s]+)/" +def extract_downstream_package(raw: str | None) -> str | None: + """Return the package name from Jira Downstream Component Name (customfield_10669). + + Modular issues store ``module:stream/package`` (e.g. ``postgresql:16/postgis``); + non-modular issues store just the package name. ``is_modular`` and + ``parse_module_stream`` match the summary against the package part only. + """ + if not raw: + return None + return raw.rsplit("/", 1)[-1] + + def is_modular(summary: str | None, component: str | None) -> bool: if not summary or not component: return False