Index only possible grouping owners and exercise real pass boundaries

Maximum-priority roots cannot be better than any group in a snapshot, so leave them out of the temporary owner indexes while retaining every resource and group. Correct new fixtures to respect existing attachment semantics: differently ranked hosts may already merge, and an agent's first-pass IP attachment must remain ambiguous until its two owners merge. Cover equal-priority Proxmox cost before any timing run.

Contract-Neutral: Candidate-only priority pruning and faithful regression fixtures preserve all stored/published resources, identity, strict attachment ambiguity, evidence precedence, public schemas and counted-system admission.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-05 04:29:10 +01:00
parent cf62ee09bc
commit ab15c4db8a
5 changed files with 57 additions and 23 deletions
@@ -5221,8 +5221,9 @@ short hostname and another reports the FQDN, but it must not collapse two
distinct fully-qualified hosts that merely share the same short prefix across
different domains.
Fallback owner buckets are pass-local priority-ordered slices, rebuilt from
each complete group snapshot. Queries visit only strictly better-priority
owners; a second distinct qualifying root still rejects attachment even when
each complete group snapshot. A maximum-priority group cannot be better than
any query and is omitted only from the temporary candidate index. Queries visit
only strictly better-priority owners; a second distinct qualifying root still rejects attachment even when
its priority or evidence kind differs. The unique root keeps lexical exact-host
evidence before full short/FQDN evidence before lexical exact-IP evidence.
Comparable/short-form keys only prune candidates: the full hostname equivalence
@@ -697,7 +697,7 @@ func TestTopLevelOwnerIndexPreservesAmbiguityInMonitoredCountAndAdmission(t *tes
t.Run(fmt.Sprintf("%d_targets", targetCount), func(t *testing.T) {
resources := []Resource{
topLevelTestProxmoxNode("platform", "tower.lab", "pve-source", "https://tower.lab:8006"),
topLevelTestTrueNAS("nas", "tower.lab"),
topLevelTestProxmoxNode("platform-other", "tower.lab", "pve-source-other", "https://tower.lab:8006"),
}
registry := NewRegistry(nil)
registry.IngestResources(resources[:targetCount])
@@ -168,12 +168,26 @@ func TestTopLevelOwnerIndexBucketsAreOrderedAndDeduplicated(t *testing.T) {
}
index := buildTopLevelSystemFallbackIndex(groups)
for name, bucket := range map[string][]int{"host": index.hosts["alpha"], "ip": index.ips["192.0.2.8"], "form": index.hostForms["alpha"]} {
if !reflect.DeepEqual(bucket, []int{9, 3, 4, 1}) {
if !reflect.DeepEqual(bucket, []int{9, 3, 4}) {
t.Fatalf("%s owners not priority/root ordered and deduplicated: %v", name, bucket)
}
}
}
func TestTopLevelOwnerIndexEqualPriorityDoesNotBuildCandidateBuckets(t *testing.T) {
groups := map[int]topLevelSystemResolvedGroup{
0: ownerTestGroup(3, []string{"alpha", "alpha.lab"}, []string{"192.0.2.8"}),
7: ownerTestGroup(3, []string{"alpha"}, []string{"192.0.2.8"}),
}
index := buildTopLevelSystemFallbackIndex(groups)
if len(index.hosts)+len(index.ips)+len(index.hostForms) != 0 {
t.Fatal("equal-priority roots cannot be candidates for any query")
}
for root := range groups {
assertOwnerTargetMatchesOracle(t, root, groups, index)
}
}
func TestTopLevelOwnerIndexEvidenceAndAmbiguity(t *testing.T) {
tests := []struct {
name string
@@ -243,7 +257,7 @@ type topLevelOwnerIndexFixture struct {
func topLevelOwnerIndexFixtures() []topLevelOwnerIndexFixture {
fixtures := []topLevelOwnerIndexFixture{{name: "empty"}, {name: "one", resources: []Resource{topLevelTestAgent("agent", "tower.lab", "machine", "agent")}}}
for _, name := range []string{"spread-1220", "shared-exact-1220", "shared-short-1220", "ambiguous-1220", "different-fqdns-200"} {
for _, name := range []string{"spread-1220", "shared-exact-1220", "shared-short-1220", "ambiguous-1220", "different-fqdns-200", "equal-priority-proxmox-1220"} {
count, nodes := 1220, 1
switch name {
case "spread-1220":
@@ -274,7 +288,11 @@ func topLevelOwnerIndexFixtures() []topLevelOwnerIndexFixture {
case "different-fqdns-200":
host = fmt.Sprintf("tower.other-%d", i)
}
resources = append(resources, topLevelTestAgent(fmt.Sprintf("agent-%d", i), host, fmt.Sprintf("machine-%d", i), fmt.Sprintf("agent-id-%d", i)))
if name == "equal-priority-proxmox-1220" {
resources = append(resources, Resource{ID: fmt.Sprintf("vm-%d", i), Type: ResourceTypeVM, Name: fmt.Sprintf("workload-%d", i), Proxmox: &ProxmoxData{NodeName: host, VMID: i + 100}})
} else {
resources = append(resources, topLevelTestAgent(fmt.Sprintf("agent-%d", i), host, fmt.Sprintf("machine-%d", i), fmt.Sprintf("agent-id-%d", i)))
}
}
fixtures = append(fixtures, topLevelOwnerIndexFixture{name: name, resources: resources})
}
+23 -7
View File
@@ -417,14 +417,25 @@ type topLevelSystemFallbackIndex struct {
}
func buildTopLevelSystemFallbackIndex(groups map[int]topLevelSystemResolvedGroup) topLevelSystemFallbackIndex {
index := topLevelSystemFallbackIndex{
hosts: make(map[string][]int),
ips: make(map[string][]int),
hostForms: make(map[string][]int),
maxPriority := 0
havePriority := false
for _, group := range groups {
if !havePriority || group.priority > maxPriority {
maxPriority = group.priority
havePriority = true
}
}
roots := make([]int, 0, len(groups))
for root := range groups {
roots = append(roots, root)
var roots []int
for root, group := range groups {
// A maximum-priority root cannot be better than any query in this
// snapshot. Omit it only from the temporary candidate index, never
// from the groups, complete inventory or output.
if group.priority < maxPriority {
roots = append(roots, root)
}
}
if len(roots) == 0 {
return topLevelSystemFallbackIndex{}
}
sort.Slice(roots, func(i, j int) bool {
if groups[roots[i]].priority != groups[roots[j]].priority {
@@ -432,6 +443,11 @@ func buildTopLevelSystemFallbackIndex(groups map[int]topLevelSystemResolvedGroup
}
return roots[i] < roots[j]
})
index := topLevelSystemFallbackIndex{
hosts: make(map[string][]int),
ips: make(map[string][]int),
hostForms: make(map[string][]int),
}
add := func(owners map[string][]int, key string, root int) {
bucket := owners[key]
// All aliases of one root are added together, so a last-entry check
@@ -437,18 +437,17 @@ func topLevelTestK8sCluster(id, clusterID, agentID, server string) Resource {
}
func TestResolveTopLevelSystemsRebuildsOwnerIndexAtEveryPassAndCall(t *testing.T) {
bridge := topLevelTestAgent("bridge", "alpha.lab", "bridge-machine", "bridge-agent")
platform := topLevelTestProxmoxNode("platform", "alpha.lab", "pve-source", "https://beta.lab:8006")
platform.Identity.IPAddresses = []string{"192.0.2.8"}
bridge := topLevelTestDockerHost("bridge", "beta.lab", "bridge-runtime", "")
bridge.Identity.IPAddresses = []string{"192.0.2.8"}
leaf := topLevelTestDockerHost("leaf", "beta.lab", "leaf-runtime", "")
leaf := topLevelTestAgent("leaf", "gamma.lab", "leaf-machine", "leaf-agent")
leaf.Identity.IPAddresses = []string{"192.0.2.8"}
resources := []Resource{
topLevelTestProxmoxNode("platform", "alpha.lab", "pve-source", "https://alpha.lab:8006"),
bridge, leaf,
topLevelTestK8sCluster("cluster", "cluster-id", "", "https://alpha.lab:6443"),
}
// Docker has better priority than the unmerged agent, so it cannot attach
// by their shared IP in the first pass. Once the agent joins the platform,
// a fresh pass must admit the IP attachment while keeping the cluster apart.
resources := []Resource{platform, bridge, leaf, topLevelTestK8sCluster("cluster", "cluster-id", "", "https://alpha.lab:6443")}
// The agent's IP initially matches two distinct better-priority groups.
// Docker can attach to the platform, but that does not change the first
// pass's candidate snapshot. A fresh pass must then admit the agent's now
// unique IP attachment while keeping the Kubernetes cluster apart.
resolver := ResolveTopLevelSystems(resources)
if resolver.Count() != 2 {
t.Fatalf("stale pass index or excluded cluster attachment: %d groups", resolver.Count())