fix: apply remaining bzlmod layering review feedback from #1618 (#1629)

Filter testonly non-root artifacts before the conflicting-force check so
that declarations layering drops cannot fail the build. Report equal-version
force displacement with an RJE_VERBOSE-gated INFO diagnostic; the non-root
declaration previously replaced the root's silently. Rename the
addtional_artifact_message variable.
diff --git a/docs/bzlmod.md b/docs/bzlmod.md
index edce4bc..400c00c 100644
--- a/docs/bzlmod.md
+++ b/docs/bzlmod.md
@@ -196,7 +196,7 @@
 the existing repository-level duplicate check, which warns or fails according to
 `duplicate_version_warning`. Forcing is the exception: if any module, the root included, sets
 `force_version` on the same coordinate at two different versions, layering will fail with an
-error message.
+error message. Non-root declarations marked `testonly` are dropped before this check.
 
 The surviving declaration is chosen by these rules:
 
@@ -314,6 +314,17 @@
 your root does not) do not produce this warning either; they are covered by the contribution
 warning above instead.
 
+One equal-version case still reports itself. When a non-root module forces the same version that
+your root module declares, its declaration displaces the root's, and an `INFO` message is printed
+when `RJE_VERBOSE` is set:
+
+```
+INFO: For dependency 'com.google.protobuf:protobuf-java' the bazel_worker_java bazel dep forces version 3.25.5; its declaration replaces the root module's declaration of the same version.
+```
+
+**Remedy:** set `force_version = True` on the root declaration to keep the root module's
+declaration, or drop the root declaration if the non-root module's is what you want.
+
 #### Which versions are reaching the repository?
 
 When more than one version of the same dependency makes it into the repository, whether declared
diff --git a/private/lib/layering.bzl b/private/lib/layering.bzl
index f1c51fb..71079a8 100644
--- a/private/lib/layering.bzl
+++ b/private/lib/layering.bzl
@@ -78,13 +78,16 @@
     coordinate_to_forced_artifact = {}
     for bazel_dep_name in bazel_dep_to_non_root_artifacts:
         module_coordinate_to_artifact = {}
-        module_artifacts = bazel_dep_to_non_root_artifacts.get(bazel_dep_name, [])
+        module_artifacts = [
+            artifact
+            for artifact in bazel_dep_to_non_root_artifacts.get(bazel_dep_name, [])
+            if not getattr(artifact, "testonly", False)
+        ]
         _fail_if_conflicting_forces(bazel_dep_name, module_artifacts)
         for artifact in module_artifacts:
-            if not getattr(artifact, "testonly", False):
-                artifact_key = to_key(artifact)
-                if _candidate_takes_precedence(module_coordinate_to_artifact.get(artifact_key), artifact):
-                    module_coordinate_to_artifact[artifact_key] = artifact
+            artifact_key = to_key(artifact)
+            if _candidate_takes_precedence(module_coordinate_to_artifact.get(artifact_key), artifact):
+                module_coordinate_to_artifact[artifact_key] = artifact
 
         for artifact_key, artifact in module_coordinate_to_artifact.items():
             if getattr(artifact, "force_version", False) and artifact_key not in root_forced_artifact_keys:
@@ -137,6 +140,7 @@
     )
 
     duplicate_artifact_warning = ""
+    forced_version_info = ""
     filtered_root_artifacts = []
     filtered_non_root_artifacts = []
     for root_artifact in root_artifacts:
@@ -161,13 +165,18 @@
                         fail(message)
                     elif duplicate_version_warning == "warn":
                         duplicate_artifact_warning = duplicate_artifact_warning + "\nWARNING: " + message
+                elif non_root_forced:
+                    forced_version_info = forced_version_info + (
+                        "\nINFO: For dependency '%s:%s' the %s bazel dep forces version %s; " % (root_artifact.group, root_artifact.artifact, bazel_dep_name, non_root_artifact.version) +
+                        "its declaration replaces the root module's declaration of the same version."
+                    )
         if keep_root_artifact:
             filtered_root_artifacts.append(root_artifact)
 
     # Add any remaining non root artifacts that weren't found in the root artifact list
-    addtional_artifact_message = ""
+    additional_artifact_message = ""
     for bazel_dep_name, non_root_artifact in non_root_coordinate_to_artifact.values():
-        addtional_artifact_message = addtional_artifact_message + (
+        additional_artifact_message = additional_artifact_message + (
             "\nINFO: The @%s repo is getting the additional artifact %s:%s:%s from the %s bazel dep." % (name, non_root_artifact.group, non_root_artifact.artifact, non_root_artifact.version, bazel_dep_name)
         )
         filtered_non_root_artifacts.append(non_root_artifact)
@@ -175,8 +184,10 @@
     diagnostics = []
     if duplicate_artifact_warning != "":
         diagnostics.append(_diagnostic(duplicate_artifact_warning, "always"))
-    if addtional_artifact_message != "":
-        diagnostics.append(_diagnostic(addtional_artifact_message, "repin_verbose"))
+    if forced_version_info != "":
+        diagnostics.append(_diagnostic(forced_version_info, "verbose"))
+    if additional_artifact_message != "":
+        diagnostics.append(_diagnostic(additional_artifact_message, "repin_verbose"))
 
     return struct(
         artifacts = filtered_root_artifacts + filtered_non_root_artifacts,
diff --git a/tests/unit/layering_test.bzl b/tests/unit/layering_test.bzl
index 1abb461..597054f 100644
--- a/tests/unit/layering_test.bzl
+++ b/tests/unit/layering_test.bzl
@@ -195,7 +195,14 @@
 
     asserts.equals(env, [non_root], result.artifacts)
     asserts.true(env, result.artifacts[0].force_version)
-    asserts.equals(env, [], result.diagnostics)
+    asserts.equals(
+        env,
+        [struct(
+            text = "\nINFO: For dependency 'com.example:library' the dep bazel dep forces version 1.0; its declaration replaces the root module's declaration of the same version.",
+            gate = "verbose",
+        )],
+        result.diagnostics,
+    )
 
     return unittest.end(env)
 
@@ -259,6 +266,44 @@
 
 testonly_nonroot_is_filtered_test = unittest.make(_testonly_nonroot_is_filtered_impl)
 
+def _testonly_force_conflicts_are_ignored_impl(ctx):
+    env = unittest.begin(ctx)
+    kept = _artifact("1.0", force_version = True)
+
+    # A testonly declaration is dropped, so its forced version cannot conflict
+    # with the surviving declaration's force.
+    asserts.equals(
+        env,
+        [kept],
+        deduplicate_non_root_artifacts(
+            {"dep": [kept, _artifact("2.0", force_version = True, testonly = True)]},
+            return_only_artifacts = True,
+        ),
+    )
+
+    return unittest.end(env)
+
+testonly_force_conflicts_are_ignored_test = unittest.make(_testonly_force_conflicts_are_ignored_impl)
+
+def _conflicting_testonly_forces_are_dropped_impl(ctx):
+    env = unittest.begin(ctx)
+
+    asserts.equals(
+        env,
+        [],
+        deduplicate_non_root_artifacts(
+            {"dep": [
+                _artifact("1.0", force_version = True, testonly = True),
+                _artifact("2.0", force_version = True, testonly = True),
+            ]},
+            return_only_artifacts = True,
+        ),
+    )
+
+    return unittest.end(env)
+
+conflicting_testonly_forces_are_dropped_test = unittest.make(_conflicting_testonly_forces_are_dropped_impl)
+
 def _multiple_nonroot_highest_wins_impl(ctx):
     env = unittest.begin(ctx)
     higher = _artifact("2.0")
@@ -833,6 +878,8 @@
         partial.make(root_force_beats_conflicting_nonroot_forces_test, size = "small"),
         partial.make(single_nonroot_survives_test, size = "small"),
         partial.make(testonly_nonroot_is_filtered_test, size = "small"),
+        partial.make(testonly_force_conflicts_are_ignored_test, size = "small"),
+        partial.make(conflicting_testonly_forces_are_dropped_test, size = "small"),
         partial.make(multiple_nonroot_highest_wins_test, size = "small"),
         partial.make(equal_version_tie_keeps_first_module_metadata_test, size = "small"),
         partial.make(root_force_beats_higher_unforced_nonroot_test, size = "small"),