Found while running a two-node cluster from a local build to verify #856.
An unlimited-tier Enterprise license carries max_cores = -1. Arc's cluster join validator only treats 0 as unlimited:
// internal/cluster/coordinator.go, validateCoreLimitForJoin
// MaxCores=0 means unlimited
if lic.MaxCores == 0 {
return nil
}
...
if projectedTotal > lic.MaxCores {
return fmt.Errorf("%w: ... license limit=%d", ErrCoreLimitExceeded, ..., lic.MaxCores)
}
With MaxCores = -1 the early return does not fire and projectedTotal > -1 is true for any node, so every join is rejected and the cluster can never form. The seed node runs alone and the joiner retries forever:
{"level":"warn","component":"cluster-coordinator",
"error":"cluster core limit exceeded: current cluster cores=14, new node cores=14, projected total=28, license limit=-1",
"node_id":"n2","core_count":14,"message":"Join rejected: core limit exceeded"}
{"level":"warn","component":"cluster-coordinator",
"error":"join rejected: cluster core limit exceeded: ... license limit=-1",
"seed":"127.0.0.1:9801","message":"Failed to join via seed"}
-1 is not an accident of one key: it is what the unlimited tier mints, and the activation server's own check uses the > 0 form (if l.MaxCores > 0 && cores > l.MaxCores). Arc's cluster-status handler agrees with the server — coordinator.go:2291 guards cores_remaining with if maxCores > 0 — so the join validator is the only place in the system that reads a non-positive limit as a real limit.
Impact: an Enterprise customer on the unlimited tier cannot form a cluster at all. A single node starts fine, which is why this has not surfaced in single-node testing.
Fix: treat any non-positive MaxCores as unlimited in validateCoreLimitForJoin, matching the two places that already do. Worth a regression test over {-1, 0, 32} so the sign convention cannot drift again.
Found while running a two-node cluster from a local build to verify #856.
An unlimited-tier Enterprise license carries
max_cores = -1. Arc's cluster join validator only treats0as unlimited:With
MaxCores = -1the early return does not fire andprojectedTotal > -1is true for any node, so every join is rejected and the cluster can never form. The seed node runs alone and the joiner retries forever:-1is not an accident of one key: it is what the unlimited tier mints, and the activation server's own check uses the> 0form (if l.MaxCores > 0 && cores > l.MaxCores). Arc's cluster-status handler agrees with the server —coordinator.go:2291guardscores_remainingwithif maxCores > 0— so the join validator is the only place in the system that reads a non-positive limit as a real limit.Impact: an Enterprise customer on the unlimited tier cannot form a cluster at all. A single node starts fine, which is why this has not surfaced in single-node testing.
Fix: treat any non-positive
MaxCoresas unlimited invalidateCoreLimitForJoin, matching the two places that already do. Worth a regression test over{-1, 0, 32}so the sign convention cannot drift again.