package app import ( "encoding/json" "io" "net/http" "strings" ) func handleTransportOwnerLocksClear(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var body TransportOwnerLocksClearRequest if r.Body != nil { defer r.Body.Close() if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&body); err != nil && err != io.EOF { http.Error(w, "bad json", http.StatusBadRequest) return } } filter, err := normalizeTransportOwnerLockClearRequest(body) if err != nil { writeJSON(w, http.StatusOK, TransportOwnerLocksClearResponse{ OK: false, Message: err.Error(), Code: "OWNER_LOCK_CLEAR_INVALID_REQUEST", }) return } transportMu.Lock() defer transportMu.Unlock() locks := loadTransportOwnerLocksState() baseRevision := locks.PolicyRevision if body.BaseRevision > 0 && body.BaseRevision != baseRevision { writeJSON(w, http.StatusOK, TransportOwnerLocksClearResponse{ OK: false, Message: "stale owner-lock revision", Code: "OWNER_LOCK_CLEAR_REVISION_MISMATCH", BaseRevision: baseRevision, }) return } matched, remaining := splitTransportOwnerLocksByFilter(locks.Items, filter) if len(matched) == 0 { writeJSON(w, http.StatusOK, TransportOwnerLocksClearResponse{ OK: true, Message: "no matching owner locks", BaseRevision: baseRevision, MatchCount: 0, ClearedCount: 0, RemainingCount: len(locks.Items), }) return } digest := digestTransportOwnerLocksClear(baseRevision, filter, matched) confirmToken := strings.TrimSpace(body.ConfirmToken) if !consumeTransportOwnerLocksClearToken(confirmToken, baseRevision, digest) { nextToken := issueTransportOwnerLocksClearToken(baseRevision, digest) code := "OWNER_LOCK_CLEAR_CONFIRM_REQUIRED" msg := "confirm token required to clear owner locks" if confirmToken != "" { code = "OWNER_LOCK_CLEAR_CONFIRM_INVALID" msg = "invalid or expired confirm token" } writeJSON(w, http.StatusOK, TransportOwnerLocksClearResponse{ OK: false, Message: msg, Code: code, BaseRevision: baseRevision, ConfirmRequired: true, ConfirmToken: nextToken, MatchCount: len(matched), RemainingCount: len(locks.Items), Items: matched, }) return } next := locks next.Items = append([]TransportOwnerLockRecord(nil), remaining...) next.PolicyRevision = baseRevision if err := saveTransportOwnerLocksState(next); err != nil { writeJSON(w, http.StatusOK, TransportOwnerLocksClearResponse{ OK: false, Message: "owner-lock save failed: " + err.Error(), Code: "OWNER_LOCK_CLEAR_SAVE_FAILED", BaseRevision: baseRevision, }) return } events.push("transport_owner_locks_cleared", map[string]any{ "base_revision": baseRevision, "cleared_count": len(matched), "remaining_count": len(next.Items), "client_id": filter.ClientID, }) writeJSON(w, http.StatusOK, TransportOwnerLocksClearResponse{ OK: true, Message: "owner locks cleared", BaseRevision: baseRevision, MatchCount: len(matched), ClearedCount: len(matched), RemainingCount: len(next.Items), Items: matched, }) }