{
  lib,
  config,
  resolveTags,
  inventory,
  clanLib,
  machineName,
  serviceConfigs,
  ...
}:
let
  serviceName = config.serviceName;
in
{
  # Roles resolution
  # : List String
  supportedRoles = clanLib.modules.getRoles "inventory.modules" inventory.modules serviceName;
  matchedRoles = builtins.attrNames (
    lib.filterAttrs (_: ms: builtins.elem machineName ms) config.machinesRoles
  );
  resolvedRolesPerInstance = lib.mapAttrs (
    instanceName: instanceConfig:
    let
      resolvedRoles = lib.genAttrs config.supportedRoles (
        roleName:
        resolveTags {
          members = instanceConfig.roles.${roleName} or { };
          inherit
            instanceName
            serviceName
            roleName
            inventory
            ;
        }
      );
      usedRoles = builtins.attrNames instanceConfig.roles;
      unmatchedRoles = builtins.filter (role: !builtins.elem role config.supportedRoles) usedRoles;
    in
    if unmatchedRoles != [ ] then
      throw ''
        Roles ${builtins.toJSON unmatchedRoles} are not defined in the service ${serviceName}.
        Instance: '${instanceName}'
        Please use one of available roles: ${builtins.toJSON config.supportedRoles}
      ''
    else
      resolvedRoles
  ) serviceConfigs;

  machinesRoles = builtins.zipAttrsWith (
    _n: vs:
    let
      flat = builtins.foldl' (acc: s: acc ++ s.machines) [ ] vs;
    in
    lib.unique flat
  ) (builtins.attrValues config.resolvedRolesPerInstance);

  assertions = lib.concatMapAttrs (
    instanceName: resolvedRoles:
    clanLib.modules.checkConstraints {
      moduleName = serviceName;
      allModules = inventory.modules;
      inherit resolvedRoles instanceName;
    }
  ) config.resolvedRolesPerInstance;
}
