The milestone in an issue is an ID in the Gitea API at creation time, in GitLab it could be the string name of the milestone. And the situation is similar with labels. When returned by the API, it could be the ID, the name or the entire object describing the lable or the milestone. There is a lot of variance. However, in every case they refer to an object defined in the project itself.
The driver implementation for Gitea (either via the API or internal) and other forges should handle this as a reference to an object that is not defined within the issue but outside of it. In the same way the user (author of the issue, assignee etc.) is. Ideally the F3 format represents this reference as a unique ID which can be used by the driver to get more information about the object, if needed, for instance because the internal representation is not just a string but an object.
For the issue provider to interpret such references, it is not enough to have access to the user and project that are provided as an argument. It needs to get milestones from the project provider. Since that would be resource consuming, it actually needs to rely on a cache maintained by the milestone provider and stored in the project provider. The bottom line is: a given provider needs to get access to the provider of the parent objects in order to get information regarding the references it contains (labels, users, etc.).
Unless there is an objection, I’m going to modify the interface so that a provider is given its parent in argument when it is created. It is then up to the concrete implementation of the driver to store the parent or discard it. Should the driver need to, the chain of these parents allows it to go up to the root provider which is currently the forge itself.
This should only be needed for caching and sharing reference related information within a driver, in ways that cannot be generalized to all drivers. It is not meant to create a hierarchy parallel to the Forge* set of structs.