104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func detectTransportOwnerSwitchConflicts(current, next []TransportPolicyIntent) []TransportConflictRecord {
|
|
curOwners := map[string]string{}
|
|
for _, raw := range current {
|
|
norm, key, _, err := normalizeTransportIntent(raw)
|
|
if err != nil || key == "" || strings.TrimSpace(norm.ClientID) == "" {
|
|
continue
|
|
}
|
|
curOwners[key] = norm.ClientID
|
|
}
|
|
if len(curOwners) == 0 {
|
|
return nil
|
|
}
|
|
|
|
conflicts := make([]TransportConflictRecord, 0, 4)
|
|
for _, raw := range next {
|
|
norm, key, _, err := normalizeTransportIntent(raw)
|
|
if err != nil || key == "" || strings.TrimSpace(norm.ClientID) == "" {
|
|
continue
|
|
}
|
|
prevOwner, exists := curOwners[key]
|
|
if !exists || prevOwner == norm.ClientID {
|
|
continue
|
|
}
|
|
conflicts = append(conflicts, TransportConflictRecord{
|
|
Key: key,
|
|
Type: "owner_switch",
|
|
Severity: "block",
|
|
Owners: []string{prevOwner, norm.ClientID},
|
|
Reason: fmt.Sprintf(
|
|
"selector owner switch %s -> %s requires explicit override",
|
|
prevOwner,
|
|
norm.ClientID,
|
|
),
|
|
SuggestedResolution: "use force_override + confirm token to switch owner",
|
|
})
|
|
}
|
|
return dedupeTransportConflicts(conflicts)
|
|
}
|
|
|
|
func transportOwnershipNeedsRebuild(policyRevision int64, owners TransportOwnershipState, planDigest string) bool {
|
|
if owners.PolicyRevision != policyRevision {
|
|
return true
|
|
}
|
|
expected := strings.TrimSpace(planDigest)
|
|
if expected == "" {
|
|
return false
|
|
}
|
|
if strings.TrimSpace(owners.PlanDigest) != expected {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func buildTransportOwnershipStateFromPlan(plan TransportPolicyCompilePlan, policyRevision int64) TransportOwnershipState {
|
|
now := time.Now().UTC().Format(time.RFC3339)
|
|
items := make([]TransportOwnershipRecord, 0, plan.RuleCount)
|
|
seen := map[string]struct{}{}
|
|
for _, iface := range plan.Interfaces {
|
|
for _, rule := range iface.Rules {
|
|
key := strings.TrimSpace(rule.SelectorType) + ":" + strings.TrimSpace(rule.SelectorValue)
|
|
if key == ":" {
|
|
continue
|
|
}
|
|
if _, ok := seen[key]; ok {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
items = append(items, TransportOwnershipRecord{
|
|
Key: key,
|
|
SelectorType: strings.TrimSpace(rule.SelectorType),
|
|
SelectorValue: strings.TrimSpace(rule.SelectorValue),
|
|
ClientID: strings.TrimSpace(rule.ClientID),
|
|
ClientKind: strings.TrimSpace(rule.ClientKind),
|
|
OwnerScope: strings.TrimSpace(rule.OwnerScope),
|
|
IfaceID: strings.TrimSpace(iface.IfaceID),
|
|
RoutingTable: strings.TrimSpace(iface.RoutingTable),
|
|
MarkHex: strings.TrimSpace(rule.MarkHex),
|
|
PriorityBase: rule.PriorityBase,
|
|
Mode: strings.TrimSpace(rule.Mode),
|
|
Priority: rule.Priority,
|
|
UpdatedAt: now,
|
|
})
|
|
}
|
|
}
|
|
sort.Slice(items, func(i, j int) bool { return items[i].Key < items[j].Key })
|
|
return TransportOwnershipState{
|
|
Version: transportStateVersion,
|
|
UpdatedAt: now,
|
|
PolicyRevision: policyRevision,
|
|
PlanDigest: digestTransportPolicyCompilePlan(plan),
|
|
Count: len(items),
|
|
Items: items,
|
|
}
|
|
}
|