platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
103
selective-vpn-api/app/transport_policy_ownership.go
Normal file
103
selective-vpn-api/app/transport_policy_ownership.go
Normal file
@@ -0,0 +1,103 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user