I see how limited controlled types allow for reference counting. I don't see how they can be used to implement circular graphs and situations where it is not clear who needs an object to continue to exist.
IIRC a circular graph would typically be done via pool-specific access types.
-- Forward declaration.
Type Element(<>);
-- Assuming there's a Graph.Pool implementation of the base Storage_Pool object.
Type Pointer is access Element
with Storage_Pool => Graph.Pool;
Subtype Handle is not null Pointer;
Type Children is array(Positive range <>) of Handle;
Type Element(Parent : Handle; Child_Count: Natural) is record
Data : Integer; -- Or whatever your actual data would be.
Link : Children( 1..Child_Count );
end record;
I haven't written enough Ada to know the answer (I've only researched the memory management scheme as part of implementing a language with compile-time ownership tracking).
I think the Ada answer would be to keep everything that has a lifetime, anything that's a resource, in its own module behind a strict limited controlled interface. But that's a very vague answer.
Maybe the semantic problem you are trying to solve should be not solved but avoided.
Much like "spaghetti code" is not only considered poor style but isn't even supported by modern languages, "spaghetti data" should also be considered a bad pattern, which more advanced languages force you to avoid.