Repository: conan-io/conan

Issue:
[bug] AssertionError when doing `conan install` (`assert not require.version_range`)
### Describe the bug

```
$ conan version
version: 2.26.2
conan_path: /home/fschoenm/.local/bin/conan
python
  version: 3.13.7
  sys_version: 3.13.7 (main, Mar  3 2026, 12:19:54) [GCC 15.2.0]
  sys_executable: /home/fschoenm/.local/share/uv/tools/conan/bin/python3
  is_frozen: False
  architecture: x86_64
system
  version: #14-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan  9 17:01:16 UTC 2026
  platform: Linux-6.17.0-14-generic-x86_64-with-glibc2.42
  system: Linux
  release: 6.17.0-14-generic
  cpu:
```

### How to reproduce it

I wanted to update a conan.lock (with `conan install ... --lockfile-partial --lockfile-out conan.lock`) and got the following assertion/error. Maybe there's a conflict between catch2 package versions somewhere (3.6.0 vs 3.13.0) but I think it shouldn't result in an assertion + stack trace but in a clear error message?

(I'm actually confused why it wants to downloads catch2 3.6.0 as that's a `test_requires` and in my understanding should be skipped or invisible from other packages. I don't know why conan decided to use it here.)

```
======== Computing dependency graph ========
Connecting to remote 'keen-cache' anonymously
catch2/3.6.0: Not found in local cache, looking in remotes...
catch2/3.6.0: Checking remote: keen-cache
catch2/3.6.0: Downloaded recipe revision 819bc5a82c2cb626916fc18ee1dbc45f
catch2/3.13.0: Not found in local cache, looking in remotes...
catch2/3.13.0: Checking remote: keen-cache
catch2/3.13.0: Downloaded recipe revision 54042604b2ac1d1ede63a53519806cf1
ERROR: Traceback (most recent call last):
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/cli/cli.py", line 297, in main
    cli.run(args)
    ~~~~~~~^^^^^^
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/cli/cli.py", line 194, in run
    command.run(self._conan_api, args[0][1:])
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/cli/command.py", line 197, in run
    info = self._method(conan_api, parser, *args)
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/cli/commands/install.py", line 48, in install
    deps_graph, lockfile, install_error = _run_install_command(conan_api, args, cwd)
                                          ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/cli/commands/install.py", line 84, in _run_install_command
    deps_graph = gapi.load_graph_consumer(path, args.name, args.version, args.user, args.channel,
                                          profile_host, profile_build, lockfile, remotes,
                                          args.update, is_build_require=args.build_require)
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/api/subapi/graph.py", line 161, in load_graph_consumer
    deps_graph = self.load_graph(root_node, profile_host=profile_host,
                                 profile_build=profile_build, lockfile=lockfile,
                                 remotes=remotes, update=update, check_update=check_updates)
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/api/subapi/graph.py", line 193, in load_graph
    deps_graph = builder.load_graph(root_node, profile_host, profile_build, lockfile)
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/internal/graph/graph_builder.py", line 55, in load_graph
    new_node = self._expand_require(require, node, dep_graph, profile_host,
                                    profile_build, graph_lock)
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/internal/graph/graph_builder.py", line 107, in _expand_require
    new_node = self._create_new_node(node, require, graph, profile_host, profile_build,
                                     graph_lock)
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/internal/graph/graph_builder.py", line 420, in _create_new_node
    node.propagate_downstream(require, new_node, graph.visibility_conflicts)
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/fschoenm/.local/share/uv/tools/conan/lib/python3.13/site-packages/conan/internal/graph/graph.py", line 139, in propagate_downstream
    assert not require.version_range  # No ranges slip into transitive_deps definitions
           ^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

ERROR:
```

## Additional acceptance criteria
- Entries in profile `[platform_requires]` and `[platform_tool_requires]` must use exact package references, not version ranges.
- If either section contains a version range, Conan must fail with a normal profile-reading error that identifies the offending section and reference.
- The CLI output for this case must not expose `AssertionError` or an internal traceback as the primary failure.


Discussion:

Fix (patch):
diff --git a/conan/internal/api/profile/profile_loader.py b/conan/internal/api/profile/profile_loader.py
index 61342c293e2..5de78063094 100644
--- a/conan/internal/api/profile/profile_loader.py
+++ b/conan/internal/api/profile/profile_loader.py
@@ -245,8 +245,16 @@ def get_profile(profile_text, base_profile=None):
         if doc.system_tools:
             ConanOutput().warning("Profile [system_tools] is deprecated,"
                                   " please use [platform_tool_requires]")
-        platform_tool_requires = [RecipeReference.loads(r) for r in doc_platform_tool_requires.splitlines()]
-        platform_requires = [RecipeReference.loads(r) for r in doc_platform_requires.splitlines()]
+
+        def parse_replaces(replaces):
+            result = [RecipeReference.loads(r) for r in replaces.splitlines()]
+            errors = [r for r in result if str(r.version).startswith("[")]
+            if errors:
+                raise ConanException("Profile [platform_requires]/[platform_tool_requires] must "
+                                     f"be exact versions, not version ranges: {errors}")
+            return result
+        platform_tool_requires = parse_replaces(doc_platform_tool_requires)
+        platform_requires = parse_replaces(doc_platform_requires)
 
         def load_replace(doc_replace_requires):
             result = {}


Test changes:
diff --git a/test/integration/graph/test_system_tools.py b/test/integration/graph/test_system_tools.py
index f24baf89070..e72b1ab88e3 100644
--- a/test/integration/graph/test_system_tools.py
+++ b/test/integration/graph/test_system_tools.py
@@ -157,6 +157,26 @@ def test_require_build_context(self):
         c.run("install app -pr:b=profile_build --build=missing")
         assert "Install finished successfully" in c.out
 
+    def test_platform_requires_error(self):
+        """
+        https://github.com/conan-io/conan/issues/19745
+        """
+        c = TestClient(light=True)
+        profile = textwrap.dedent("""\
+            include(default)
+
+            [platform_tool_requires]
+            cmake/[*]
+            """)
+        c.save({"conanfile.py": GenConanfile("catch2").with_tool_requires("cmake/[*]"),
+                "profile": profile})
+
+        c.run("create  --version=3.6.0 -pr=profile", assert_error=True)
+        assert "AssertionError" not in c.out
+        assert ("ERROR: Error reading 'profile' profile: Profile [platform_requires]/"
+                "[platform_tool_requires] must be exact versions, "
+                "not version ranges: [cmake/[*]]") in c.out
+
 
 class TestToolRequiresLock: