package server import ( "database/sql" "database/sql/driver" "errors" "net/http" "agent-system-backend/internal/store" "agent-system-backend/pkg/extension" ) // explainTargetParams would select another actor; the endpoint refuses them // outright so the response never depends on whether that actor exists. var explainTargetParams = []string{"owner", "userId", "user", "actor"} func (s *Server) explainCapability(w http.ResponseWriter, r *http.Request) { if SessionScope(r) == store.SessionScopeSetPasswordOnly { http.Error(w, "capability explain denied", http.StatusForbidden) return } query := r.URL.Query() for _, key := range explainTargetParams { if query.Has(key) { http.Error(w, "capability explain is limited to the current actor", http.StatusForbidden) return } } explanation, err := s.authorizer.ExplainCapabilities(s.owner(r)) if errors.Is(err, store.ErrAccountForbidden) { http.Error(w, "capability explain denied", http.StatusForbidden) return } if err != nil { http.Error(w, "capability explain unavailable", http.StatusServiceUnavailable) return } writeJSON(w, http.StatusOK, explanation) } // categorizeWorkFailure maps Backend and provider errors for operation into // one WorkFailure category. Uncategorized errors, notably not-found, keep the // existing handling so a refusal never reveals another project or account. func categorizeWorkFailure(operation string, err error) (extension.WorkFailure, bool) { var failure extension.WorkFailure switch { case err == nil: return extension.WorkFailure{}, false case errors.As(err, &failure) && failure.StatusCode() != 0: return extension.CategorizeWorkFailure(operation, err) case errors.Is(err, store.ErrAccountForbidden): return extension.WorkFailure{Operation: operation, Reason: extension.WorkFailurePolicyRejected}, true case operation != extension.WorkOperationConversation && errors.Is(err, store.ErrConflict): return extension.WorkFailure{Operation: operation, Reason: extension.WorkFailureDuplicateName}, true case errors.Is(err, sql.ErrConnDone), errors.Is(err, driver.ErrBadConn): return extension.WorkFailure{Operation: operation, Reason: extension.WorkFailureServiceUnavailable}, true } return extension.CategorizeWorkFailure(operation, err) } func writeWorkFailure(w http.ResponseWriter, operation string, err error) { if failure, ok := categorizeWorkFailure(operation, err); ok { http.Error(w, failure.Error(), failure.StatusCode()) return } writeProjectErr(w, err) } func writeWorkJSON(w http.ResponseWriter, operation string, v any, err error) { if err != nil { writeWorkFailure(w, operation, err) return } writeJSON(w, http.StatusOK, v) }