package app import ( "encoding/json" "fmt" "io" "net/http" "time" ) func handleTransportPoliciesValidateExec(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var body TransportPolicyValidateRequest if r.Body != nil { defer r.Body.Close() if err := json.NewDecoder(io.LimitReader(r.Body, 2<<20)).Decode(&body); err != nil && err != io.EOF { http.Error(w, "bad json", http.StatusBadRequest) return } } transportMu.Lock() clientsState := loadTransportClientsState() current := loadTransportPolicyState() locks := TransportOwnerLockState{} if transportPolicyKernelConntrackStickyEnabled() { locks = loadTransportOwnerLocksState() } transportMu.Unlock() clients := transportPolicyClientsWithVirtualTargets(clientsState.Items) result := validateTransportPolicy(body.Intents, current.Intents, clients) ownerSwitchConflicts := detectTransportOwnerSwitchConflicts(current.Intents, result.Normalized) if len(ownerSwitchConflicts) > 0 { result.Conflicts = append(result.Conflicts, ownerSwitchConflicts...) result.Conflicts = dedupeTransportConflicts(result.Conflicts) result.Summary = summarizeTransportConflicts(result.Conflicts) result.Valid = result.Summary.BlockCount == 0 } ownerLockConflicts := detectTransportOwnerLockConflicts(current.Intents, result.Normalized, clients) if len(ownerLockConflicts) > 0 { result.Conflicts = append(result.Conflicts, ownerLockConflicts...) result.Conflicts = dedupeTransportConflicts(result.Conflicts) result.Summary = summarizeTransportConflicts(result.Conflicts) result.Valid = result.Summary.BlockCount == 0 } destinationLockConflicts := detectTransportDestinationLockConflicts(current.Intents, result.Normalized, locks) if len(destinationLockConflicts) > 0 { result.Conflicts = append(result.Conflicts, destinationLockConflicts...) result.Conflicts = dedupeTransportConflicts(result.Conflicts) result.Summary = summarizeTransportConflicts(result.Conflicts) result.Valid = result.Summary.BlockCount == 0 } if body.BaseRevision > 0 && body.BaseRevision != current.Revision { result.Conflicts = append(result.Conflicts, TransportConflictRecord{ Key: "base_revision", Type: "stale_base", Severity: "warn", Reason: fmt.Sprintf("base_revision=%d differs from current=%d", body.BaseRevision, current.Revision), SuggestedResolution: "refresh policy and retry validate/apply", }) result.Summary.WarnCount++ result.Conflicts = dedupeTransportConflicts(result.Conflicts) result.Summary = summarizeTransportConflicts(result.Conflicts) result.Valid = result.Summary.BlockCount == 0 } plan, compileConflicts := compileTransportPolicyPlan(result.Normalized, clients, current.Revision) if len(compileConflicts) > 0 { result.Conflicts = append(result.Conflicts, compileConflicts...) result.Conflicts = dedupeTransportConflicts(result.Conflicts) result.Summary = summarizeTransportConflicts(result.Conflicts) result.Valid = result.Summary.BlockCount == 0 } digest := digestTransportIntents(result.Normalized) confirmToken := issueTransportConfirmToken(current.Revision, digest) state := TransportConflictState{ Version: transportStateVersion, UpdatedAt: time.Now().UTC().Format(time.RFC3339), HasBlocking: result.Summary.BlockCount > 0, Items: append([]TransportConflictRecord(nil), result.Conflicts...), } transportMu.Lock() _ = saveTransportConflictsState(state) transportMu.Unlock() msg := "validation complete" code := "" if result.Summary.BlockCount > 0 { msg = "policy has blocking conflicts" code = "POLICY_CONFLICT_BLOCK" } events.push("transport_policy_validated", map[string]any{ "valid": result.Valid, "block_count": result.Summary.BlockCount, "warn_count": result.Summary.WarnCount, }) if result.Summary.BlockCount > 0 { events.push("transport_conflict_detected", map[string]any{ "count": result.Summary.BlockCount, }) } writeJSON(w, http.StatusOK, TransportPolicyValidateResponse{ OK: true, Message: msg, Code: code, Valid: result.Valid, BaseRevision: current.Revision, ConfirmToken: confirmToken, Summary: result.Summary, Conflicts: result.Conflicts, Diff: result.Diff, Plan: &plan, }) }