From 4ea0fe88af3176a65b6c27f6070951141129c36a Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Fri, 11 Sep 2026 23:01:13 +0500 Subject: [PATCH] fix(workflows): state the equal-priority tie-break in 'workflow resolve' output `workflow resolve` prints the layer list under a bare header: console.print("Layers (highest precedence first):") `collect_all_layers` sorts by `(priority, source)` ascending, which puts the winning layer first only while priorities DIFFER. On a tie the sort is alphabetical by source, while the merge gives the conflict to the LAST id (docs/reference/workflows.md:126: "Equal-priority overlays are applied alphabetically by ID, with the last ID winning conflicts"). So for a tie the header stated the opposite of the outcome printed directly beneath it, in the one command whose job is explaining which overlay won: DIFFERENT priorities (alpha=5, beta=10) listed first : project:alpha actual winner: echo FROM-ALPHA header correct? YES EQUAL priorities (both 10) listed first : project:alpha actual winner: echo FROM-BETA header correct? NO The ordering itself is deliberate and pinned by `test_workflow_resolve_equal_priority_layers_sort_by_source`, so this spells the tie-break out rather than reordering the list. Output is now self-consistent: Layers (highest precedence first; on equal priority the last ID wins): - [project-overlay] project:alpha (priority=10) - [project-overlay] project:beta (priority=10) Step attribution: - build: project:beta Co-Authored-By: Claude Opus 5 (1M context) --- .../workflows/overlays/_commands.py | 12 ++++- tests/workflows/test_overlay_commands.py | 53 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/workflows/overlays/_commands.py b/src/specify_cli/workflows/overlays/_commands.py index 06c4dca835..17e2fa4435 100644 --- a/src/specify_cli/workflows/overlays/_commands.py +++ b/src/specify_cli/workflows/overlays/_commands.py @@ -424,7 +424,17 @@ def workflow_resolve(project_root: Path, workflow_id: str) -> dict[str, Any] | N return None console.print(f"Resolved workflow '{workflow_id}':") - console.print("Layers (highest precedence first):") + # The list is sorted by (priority, source) ascending, which puts the + # winning layer first only while priorities DIFFER. On a tie the sort is + # alphabetical by source while the merge gives the conflict to the LAST id + # (docs/reference/workflows.md:126), so the plain "highest precedence + # first" label stated the opposite of the outcome printed directly beneath + # it in the same output -- in the one command whose job is explaining which + # overlay won. Spell the tie-break out rather than reorder the list, which + # `test_workflow_resolve_equal_priority_layers_sort_by_source` pins. + console.print( + "Layers (highest precedence first; on equal priority the last ID wins):" + ) for layer in layers: priority = ( "n/a" if layer.tier == "base" else str(normalize_priority(layer.priority)) diff --git a/tests/workflows/test_overlay_commands.py b/tests/workflows/test_overlay_commands.py index c28f53b050..ec28559144 100644 --- a/tests/workflows/test_overlay_commands.py +++ b/tests/workflows/test_overlay_commands.py @@ -698,6 +698,59 @@ def test_workflow_resolve_escapes_rich_markup_in_step_id( assert result.exit_code == 0, result.output assert step_id in result.output + def test_workflow_resolve_header_states_the_equal_priority_tiebreak( + self, project_dir, monkeypatch + ): + """The layer header must not contradict the attribution beneath it. + + The list is sorted by (priority, source) ascending, which puts the + winning layer first only while priorities DIFFER. On a tie the sort is + alphabetical while the merge gives the conflict to the LAST id, so a + bare "highest precedence first" label stated the opposite of the + outcome printed directly below it — in the one command whose job is + explaining which overlay won. + """ + monkeypatch.setattr("specify_cli._require_specify_project", lambda: project_dir) + _write_workflow( + project_dir, + "wf", + { + "schema_version": "1.0", + "workflow": {"id": "wf", "name": "WF", "version": "1.0.0"}, + "steps": [{"id": "build", "type": "command", "command": "echo BASE"}], + }, + ) + for overlay_id in ("alpha", "beta"): + _write_overlay( + project_dir, + "wf", + overlay_id, + { + "id": overlay_id, + "extends": "wf", + "priority": 10, + "edits": [ + { + "operation": "replace", + "anchor": "build", + "step": { + "id": "build", + "type": "command", + "command": f"echo FROM-{overlay_id.upper()}", + }, + } + ], + }, + ) + + result = runner.invoke(app, ["workflow", "resolve", "wf"]) + + assert result.exit_code == 0, result.output + output = " ".join(result.output.split()) + # alpha is listed first, but beta is the layer that actually wins. + assert "build: project:beta" in output, output + assert "on equal priority the last ID wins" in output, output + def test_workflow_resolve_equal_priority_layers_sort_by_source(self, project_dir, monkeypatch): """Equal-priority overlays are listed alphabetically by source.""" monkeypatch.setattr("specify_cli._require_specify_project", lambda: project_dir)