fix: honour pinned versions in external resolvers (#1606)
diff --git a/docs/bzlmod-api.md b/docs/bzlmod-api.md
index 399d03d..38e7474 100644
--- a/docs/bzlmod-api.md
+++ b/docs/bzlmod-api.md
@@ -114,7 +114,7 @@
 | <a id="maven.install-strict_visibility_value"></a>strict_visibility_value |  -   | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional |  `["@rules_jvm_external//visibility:private"]`  |
 | <a id="maven.install-use_credentials_from_home_netrc_file"></a>use_credentials_from_home_netrc_file |  Whether to pass machine login credentials from the ~/.netrc file to coursier.   | Boolean | optional |  `False`  |
 | <a id="maven.install-use_starlark_android_rules"></a>use_starlark_android_rules |  Whether to use the native or Starlark version of the Android rules.   | Boolean | optional |  `False`  |
-| <a id="maven.install-version_conflict_policy"></a>version_conflict_policy |  Policy for user-defined vs. transitive dependency version conflicts<br><br>If "pinned", choose the user-specified version in maven_install unconditionally. If "default", follow Coursier's default policy.   | String | optional |  `"default"`  |
+| <a id="maven.install-version_conflict_policy"></a>version_conflict_policy |  Policy for user-defined vs. transitive dependency version conflicts<br><br>If "pinned", choose the user-specified version in maven_install unconditionally. With the Gradle and Maven resolvers, this only applies to artifacts contributed by the root module. If "default", follow the selected resolver's default policy.   | String | optional |  `"default"`  |
 
 <a id="maven.override"></a>
 
diff --git a/private/extensions/maven.bzl b/private/extensions/maven.bzl
index a9a148b..0aa500d 100644
--- a/private/extensions/maven.bzl
+++ b/private/extensions/maven.bzl
@@ -110,7 +110,8 @@
             doc = """Policy for user-defined vs. transitive dependency version conflicts
 
             If "pinned", choose the user-specified version in maven_install unconditionally.
-            If "default", follow Coursier's default policy.
+            With the Gradle and Maven resolvers, this only applies to artifacts contributed by the root module.
+            If "default", follow the selected resolver's default policy.
             """,
             default = "default",
             values = [
@@ -597,6 +598,16 @@
         if k != "to_json" and k != "to_proto" and getattr(s, k, None)
     } | {"version": getattr(s, "version", "")}
 
+def apply_root_version_conflict_policy(artifacts, resolver, version_conflict_policy):
+    """Applies the install-level conflict policy to root module artifacts."""
+    if resolver not in ["gradle", "maven"] or version_conflict_policy != "pinned":
+        return artifacts
+
+    return [
+        struct(**(remove_fields(artifact) | {"force_version": True})) if getattr(artifact, "version", None) else artifact
+        for artifact in artifacts
+    ]
+
 def maven_impl(mctx):
     repos = {}
     overrides = {}
@@ -652,7 +663,11 @@
         merged_repo.update(root_repo)
 
         # Special handling for artifacts and boms - deduplicate with root priority
-        root_artifacts = root_repo.get("artifacts", [])
+        root_artifacts = apply_root_version_conflict_policy(
+            root_repo.get("artifacts", []),
+            root_repo.get("resolver", _DEFAULT_RESOLVER),
+            root_repo.get("version_conflict_policy", "default"),
+        )
         bazel_dep_to_non_root_artifacts = non_root_repo.get("bazel_dep_to_artifacts", {})
         root_boms = root_repo.get("boms", [])
         bazel_dep_to_non_root_boms = non_root_repo.get("bazel_dep_to_boms", {})
diff --git a/tests/unit/BUILD b/tests/unit/BUILD
index 7ca05ab..f50a066 100644
--- a/tests/unit/BUILD
+++ b/tests/unit/BUILD
@@ -11,6 +11,7 @@
 load(":specs_test.bzl", "artifact_specs_test_suite")
 load(":v3_lock_file_test.bzl", "v3_lock_file_test_suite")
 load(":version_catalogs_test.bzl", "version_catalogs_test_suite")
+load(":version_conflict_policy_test.bzl", "version_conflict_policy_test_suite")
 
 amend_artifact_test_suite()
 
@@ -38,3 +39,4 @@
 
 version_catalogs_test_suite()
 
+version_conflict_policy_test_suite()
diff --git a/tests/unit/version_conflict_policy_test.bzl b/tests/unit/version_conflict_policy_test.bzl
new file mode 100644
index 0000000..26b7100
--- /dev/null
+++ b/tests/unit/version_conflict_policy_test.bzl
@@ -0,0 +1,48 @@
+"""Tests for resolver-specific version conflict policies."""
+
+load("@bazel_skylib//lib:partial.bzl", "partial")
+load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
+load("//private/extensions:maven.bzl", "apply_root_version_conflict_policy")
+load("//private/lib:coordinates.bzl", "unpack_coordinates")
+
+def _pinned_policy_forces_versioned_root_artifacts_impl(ctx):
+    env = unittest.begin(ctx)
+
+    for resolver in ["gradle", "maven"]:
+        versioned = unpack_coordinates("com.example:root:1.0")
+        versionless = unpack_coordinates("com.example:managed-by-bom")
+
+        artifacts = apply_root_version_conflict_policy(
+            [versioned, versionless],
+            resolver,
+            "pinned",
+        )
+
+        asserts.true(env, artifacts[0].force_version)
+        asserts.false(env, hasattr(artifacts[1], "force_version"))
+
+    return unittest.end(env)
+
+pinned_policy_forces_versioned_root_artifacts_test = unittest.make(_pinned_policy_forces_versioned_root_artifacts_impl)
+
+def _other_policies_leave_root_artifacts_unchanged_impl(ctx):
+    env = unittest.begin(ctx)
+
+    artifact = unpack_coordinates("com.example:root:1.0")
+
+    default_artifacts = apply_root_version_conflict_policy([artifact], "gradle", "default")
+    coursier_artifacts = apply_root_version_conflict_policy([artifact], "coursier", "pinned")
+
+    asserts.false(env, hasattr(default_artifacts[0], "force_version"))
+    asserts.false(env, hasattr(coursier_artifacts[0], "force_version"))
+
+    return unittest.end(env)
+
+other_policies_leave_root_artifacts_unchanged_test = unittest.make(_other_policies_leave_root_artifacts_unchanged_impl)
+
+def version_conflict_policy_test_suite():
+    unittest.suite(
+        "version_conflict_policy_tests",
+        partial.make(pinned_policy_forces_versioned_root_artifacts_test, size = "small"),
+        partial.make(other_policies_leave_root_artifacts_unchanged_test, size = "small"),
+    )