void offer(List<NodeCandidate> candidates) {
        for (NodeCandidate candidate : candidates) {
            if (candidate.allocation().isPresent()) {
                Allocation allocation = candidate.allocation().get();
                ClusterMembership membership = allocation.membership();
                if ( ! allocation.owner().equals(application)) continue; // wrong application
                if ( ! membership.cluster().satisfies(cluster)) continue; // wrong cluster id/type
                if (candidate.state() == Node.State.active && allocation.removable()) continue; // don't accept; causes removal
                if (candidate.state() == Node.State.active && candidate.wantToFail()) continue; // don't accept; causes failing
                if (indexes.contains(membership.index())) continue; // duplicate index (just to be sure)
                if (nodeRepository.zone().cloud().allowEnclave() && candidate.parent.isPresent() && ! candidate.parent.get().cloudAccount().equals(requested.cloudAccount())) continue; // wrong account

                boolean resizeable = requested.considerRetiring() && candidate.isResizable;

                if (( ! saturated() && hasCompatibleResources(candidate) && requested.acceptable(candidate)) || acceptIncompatible(candidate)) {
                    candidate = candidate.withNode();
                    if (candidate.isValid())
                        acceptNode(candidate, shouldRetire(candidate, candidates), resizeable);
                }
            }
            else if ( ! saturated() && hasCompatibleResources(candidate)) {
                if ( ! nodeRepository.nodeResourceLimits().isWithinRealLimits(candidate, cluster)) {
                    ++rejectedDueToInsufficientRealResources;
                    continue;
                }
                if ( violatesParentHostPolicy(candidate)) {
                    ++rejectedDueToClashingParentHost;
                    continue;
                }
                switch (violatesExclusivity(candidate)) {
                    case PARENT_HOST_NOT_EXCLUSIVE -> candidate = candidate.withExclusiveParent(true);
                    case NONE -> {}
                    case YES -> {
                        ++rejectedDueToExclusivity;
                        continue;
                    }
                }
                if (candidate.wantToRetire()) {
                    continue;
                }
                if (requested.type() == NodeType.tenant && !requested.canFail()) {
                    continue; // Cannot allocate nodes when bootstrapping
                }
                candidate = candidate.allocate(application,
                                               ClusterMembership.from(cluster, nextIndex.get()),
                                               requested.resources().orElse(candidate.resources()),
                                               nodeRepository.clock().instant());
                if (candidate.isValid()) {
                    acceptNode(candidate, Retirement.none, false);
                }
            }
        }
    }
Offer some nodes to this. The nodes may have an allocation to a different application or cluster,
an allocation to this cluster, or no current allocation (in which case one is assigned).

Note that if unallocated nodes are offered before allocated nodes, this will unnecessarily
reject allocated nodes due to index duplicates.

@param candidates the nodes which are potentially on offer. These may belong to a different application etc.
node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java