I would implement it this way: The solve proceeds as today, except the upper cap is ignored (option 2 and option 3). Then (option 2 only), once the final package is selected, you’d check to see if the current Python version is valid. If not, throw an error. The upper cap does not affect the solve at all, it just causes an error at the end if it is not valid. You are just removing the possibility to back solve to get an older package with a looser Python upper bound, because older packages are very, very unlikely to have better support for newer Python versions.
Honestly, the same system could be applied for requirements. You could also specify that you should never back-solve for looser requirements. Because the current IPython caps Jedi to 0.17 doesn’t mean you should look for an older IPython that doesn’t cap Jedi to 0.17, it’s not more likely to be compatible. But implementing it is a bit harder, since package versions are not fixed and limits can be valid (just back solving for looser limits is problematic), while the Python version is fixed. And maybe there might be more valid reasons.
There could be hundreds of old releases, requirements change over time, every file is allowed to have different requirements - I’m not sure this would be “simple”. And you’d have to add it for all time or it’s useless, the solver would just go back to wherever you gave up editing requirements.
Someone other than me can pursue this:
Files are hashed; you can never upload two different wheels with the same name but different hashes. Lock files / requirements.txt hashes are listed for each file. You can’t change the files; that’s a core security requirement. So the only way to modify the metadata would be to add a new file that sits “alongside” the wheel or SDist file - probably each one, since each file technically could have different metadata, and then modify setuptools, flit, and every other build system to produce them, pip and every other installer to include them, pypi and every other wheelhouse to include them, pip-tools, Poetry, Pipenv, PDM, and every other locking system to handle them somehow, etc. And the incoming PEP 665. And any older version of anything (like pip) would not respect the new metadata.
And, you’d have to be very careful to only allow a very limited subset of changes - you wouldn’t want an update of a package to add a new malicious dependency via metadata!
Would you be able to get old versions of dependency override files too? Plus other questions would need to be answered. It could be done, but it would be a major undertaking, for a rather small benefit - ideally, everyone should try to avoid capping things, play nicely with deprecation periods, test alpha/beta/rc, and just understand that in the real world, a library can’t perfectly specify it’s dependencies so that there will never be a breakage. That’s only possible when locking, for applications.