pub async fn expand_roles<S: StorageAdapter>(
storage: &S,
principal: RolePrincipal<'_>,
) -> Result<Vec<RoleName>, ParseError>Expand description
Every role a principal holds, direct and transitive, deduplicated.
The order is upstream’s: direct role names first, in the order the _Role rows came back,
then each level of ancestors in turn. Duplicates are dropped on first sight, which is what
[...new Set(names)] does (Auth.js:402).
The traversal cuts cycles, it does not reject them. Upstream marks each role objectId into
queriedRoles as it builds the next frontier and filters the frontier against it
(Auth.js:393-398), so A -> B -> A terminates with both names and no error. A cycle check
that raised would be a behavior change, and no cycle check at all is an infinite loop. The
marking is the whole mechanism; see a_cycle_terminates_and_returns_both_names.
Depth is unbounded and the cost is per level, not per role. Each level is one read of the
join collection followed by one read of _Role, regardless of how wide the frontier is. A
per-role query would turn a wide role graph into a query storm.