Documentation
¶
Overview ¶
Package controlplane owns Stella's deployment control-plane resources: LLM providers, deployment settings (embedding, CLI-tool registry, OAuth provider config), plugins (registered + manifest), and channels.
These resources are administered, not user-owned. Authorization is a single admin gate at Begin (see access.go): an Access is minted only for an admin authority, so a non-admin actor is denied before any durable read or external action — identical to the legacy requireAdmin gate this service replaces. There is no per-user ownership and no per-method authorization.
The service also owns the persistence and live-reload orchestration each control-plane mutation performs (store writes plus pool/plugin-host hot reloads), so the HTTP transport keeps only request decoding and response shaping. It never reads request-supplied identity: the caller passes a trusted authz.Authority derived from verified session claims.
Index ¶
- Constants
- Variables
- type Access
- func (a *Access) CliToolLatest(ctx context.Context, tool string) (string, error)
- func (a *Access) CreateProvider(ctx context.Context, p config.Provider) error
- func (a *Access) DeleteChannel(ctx context.Context, id string) error
- func (a *Access) DeleteManifestPlugin(ctx context.Context, id string) error
- func (a *Access) DeleteOAuthProviderConfig(ctx context.Context, id string) error
- func (a *Access) DeleteProvider(ctx context.Context, id string) error
- func (a *Access) FetchProviderModels(ctx context.Context, id, apiKey, baseURL string) ([]ProviderModelItem, error)
- func (a *Access) GetChannel(ctx context.Context, id string) (config.Channel, error)
- func (a *Access) GetEmbeddingSettings(ctx context.Context) (config.EmbeddingSettings, error)
- func (a *Access) GetOAuthProviderConfig(ctx context.Context, id string) (connections.OAuthProviderConfig, error)
- func (a *Access) GetPluginConfig(ctx context.Context, kind, name string) (map[string]any, error)
- func (a *Access) GetPluginConfigSchema(ctx context.Context, kind, name string) (map[string]any, error)
- func (a *Access) GetPluginStatus(ctx context.Context, kind, name string) (any, error)
- func (a *Access) GetProvider(ctx context.Context, id string) (config.Provider, error)
- func (a *Access) GetVisionSettings(ctx context.Context) (config.VisionSettings, error)
- func (a *Access) ListChannels(ctx context.Context) ([]config.Channel, error)
- func (a *Access) ListManifestPlugins(ctx context.Context) (*manifestplugins.Manifest, error)
- func (a *Access) ListPlugins(ctx context.Context) ([]pkgplugins.RegisteredPlugin, error)
- func (a *Access) ListProviderModels(ctx context.Context, id string) ([]ProviderModelItem, error)
- func (a *Access) ListProviderTypes() ([]pluginhost.ProviderType, error)
- func (a *Access) ListProviders(ctx context.Context) ([]config.Provider, error)
- func (a *Access) LookupAgent(ctx context.Context, id string) (config.Agent, error)
- func (a *Access) ManageChannel(id string) (*ChannelManagement, error)
- func (a *Access) ResetManifestPlugin(ctx context.Context, id, field string) (*manifestplugins.Manifest, error)
- func (a *Access) SaveChannel(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
- func (a *Access) SaveManifestPluginDefinition(ctx context.Context, plugin manifestplugins.ManifestPlugin, fields []string) (*manifestplugins.Manifest, error)
- func (a *Access) SearchCliToolRegistry(ctx context.Context, query string, limit int) ([]manifestplugins.RegistryTool, error)
- func (a *Access) SetEmbeddingSettings(ctx context.Context, upd EmbeddingUpdate) (config.EmbeddingSettings, error)
- func (a *Access) SetManifestPluginEnabled(ctx context.Context, id string, enabled bool) (*manifestplugins.Manifest, error)
- func (a *Access) SetOAuthProviderConfig(ctx context.Context, cfg connections.OAuthProviderConfig) (connections.OAuthProviderConfig, error)
- func (a *Access) SetVisionSettings(ctx context.Context, s config.VisionSettings) (config.VisionSettings, error)
- func (a *Access) SyncManifestPlugins(ctx context.Context) (manifestplugins.ReconcileResult, error)
- func (a *Access) TogglePlugin(ctx context.Context, kind, name string, enabled bool) (config.Plugin, error)
- func (a *Access) UpdatePluginConfig(ctx context.Context, kind, name string, cfg map[string]any) (config.Plugin, error)
- func (a *Access) UpdateProvider(ctx context.Context, id string, p config.Provider) (config.Provider, error)
- type AgentChannels
- func (a *AgentChannels) DeleteChannel(ctx context.Context, id string) error
- func (a *AgentChannels) GetChannel(ctx context.Context, id string) (config.Channel, error)
- func (a *AgentChannels) ListChannels(ctx context.Context) ([]config.Channel, error)
- func (a *AgentChannels) SaveChannel(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
- type ChannelAccess
- type ChannelBinding
- type ChannelManagement
- func (m *ChannelManagement) Channel(ctx context.Context, id string) (config.Channel, error)
- func (m *ChannelManagement) Save(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
- func (m *ChannelManagement) ValidateBinding(ctx context.Context, ch config.Channel) error
- type ConflictError
- type EmbeddingUpdate
- type ForbiddenError
- type NotFoundError
- type ProviderModelItem
- type PublicChannel
- type Service
- func (s *Service) Begin(_ context.Context, authority authz.Authority) (*Access, error)
- func (s *Service) BeginAgentChannels(_ context.Context, authority authz.Authority, managedAgentIDs []string) (*AgentChannels, error)
- func (s *Service) BeginChannelBinding(ctx context.Context, authority authz.Authority, channelID string) (*ChannelBinding, error)
- func (s *Service) DisabledChannelTypes(ctx context.Context, authority authz.Authority) (map[string]bool, error)
- func (s *Service) ListEnabledModels(ctx context.Context, authority authz.Authority) ([]config.CachedModel, error)
- func (s *Service) PublicChannels(ctx context.Context, authority authz.Authority) ([]PublicChannel, error)
- type UpstreamError
- type ValidationError
Constants ¶
const ChannelPluginConfigError = "channel instance config lives on /channels, not plugin config"
ChannelPluginConfigError is the client message returned when an admin tries to read or write a channel plugin's config through the plugin API; channel instance config lives on the /channels surface instead.
Variables ¶
ErrUnavailable is returned when the service itself is not configured (a nil receiver); the transport maps it to a fail-closed 503.
Functions ¶
This section is empty.
Types ¶
type Access ¶
type Access struct {
// contains filtered or unexported fields
}
Access is one authorized control-plane use case. Every control-plane resource (providers, settings, plugins, channels) is admin-only, so authorization is a single gate at Begin: an Access exists only for an admin authority. Each method then performs the durable write and hot-reload the legacy handler did, with no further per-method authorization.
func (*Access) CliToolLatest ¶
CliToolLatest resolves the latest installable version for a mise tool key.
func (*Access) CreateProvider ¶
CreateProvider persists a new provider and hot-reloads the pool.
func (*Access) DeleteChannel ¶
DeleteChannel stops a channel's runtime and removes it.
func (*Access) DeleteManifestPlugin ¶ added in v0.63.2
DeleteManifestPlugin removes an admin-added plugin: its override row is the whole definition, so dropping it drops the plugin. A builtin ships with the server and is refused — disabling it is the reversible equivalent, and letting a delete "remove" one would only resurrect it on the next resolve. Installed binaries stay in the mise cache, exactly as they do when a plugin is disabled.
func (*Access) DeleteOAuthProviderConfig ¶
DeleteOAuthProviderConfig removes an OAuth provider client configuration.
func (*Access) DeleteProvider ¶
DeleteProvider removes a provider and hot-reloads the pool.
func (*Access) FetchProviderModels ¶
func (a *Access) FetchProviderModels(ctx context.Context, id, apiKey, baseURL string) ([]ProviderModelItem, error)
FetchProviderModels lists the provider's models from its live API, refreshes the cached set, and returns the merged list. Credentials fall back to the stored provider when the caller omits them. It preserves the legacy status split: a missing provider during the credential fallback is a 400 "api_key is required", while the authoritative load is a 404.
func (*Access) GetChannel ¶
GetChannel returns one channel by id (opaque 404 when missing).
func (*Access) GetEmbeddingSettings ¶
GetEmbeddingSettings returns the deployment-wide embedding configuration.
func (*Access) GetOAuthProviderConfig ¶
func (a *Access) GetOAuthProviderConfig(ctx context.Context, id string) (connections.OAuthProviderConfig, error)
GetOAuthProviderConfig returns the stored OAuth provider client configuration.
func (*Access) GetPluginConfig ¶
GetPluginConfig returns a plugin's stored config. Channel plugins are rejected: their instance config lives on /channels.
func (*Access) GetPluginConfigSchema ¶
func (a *Access) GetPluginConfigSchema(ctx context.Context, kind, name string) (map[string]any, error)
GetPluginConfigSchema returns a plugin's admin config schema.
func (*Access) GetPluginStatus ¶
GetPluginStatus returns a plugin's admin status payload.
func (*Access) GetProvider ¶
GetProvider returns one provider by id (opaque 404 when missing).
func (*Access) GetVisionSettings ¶ added in v0.61.0
GetVisionSettings returns the deployment-wide image-understanding configuration.
func (*Access) ListChannels ¶
func (*Access) ListManifestPlugins ¶
ListManifestPlugins returns the builtin manifest overlaid with DB overrides.
func (*Access) ListPlugins ¶
func (a *Access) ListPlugins(ctx context.Context) ([]pkgplugins.RegisteredPlugin, error)
ListPlugins returns the admin-visible registered plugins. The transport shapes each into its admin view.
func (*Access) ListProviderModels ¶
ListProviderModels returns the merged custom+fetched model list for a provider.
func (*Access) ListProviderTypes ¶
func (a *Access) ListProviderTypes() ([]pluginhost.ProviderType, error)
ListProviderTypes returns the provider plugin types the deployment can add.
func (*Access) ListProviders ¶
ListProviders returns every configured LLM provider.
func (*Access) LookupAgent ¶
LookupAgent reads an agent for a channel-binding precondition: a registration flow binds a channel to an agent that must exist and be enabled. It is an admin-gated read (the Access already passed Begin), so a non-admin never reaches it; the caller inspects the returned agent's Enabled flag.
func (*Access) ManageChannel ¶
func (a *Access) ManageChannel(id string) (*ChannelManagement, error)
ManageChannel opens a channel-management operation. The admin gate already ran at Begin, so this only hands back the operation handle; id is retained for call-site symmetry (registration flows keep this handle across their platform handshake, then call Save after credentials arrive).
func (*Access) ResetManifestPlugin ¶ added in v0.63.2
func (a *Access) ResetManifestPlugin(ctx context.Context, id, field string) (*manifestplugins.Manifest, error)
ResetManifestPlugin hands a builtin's definition back to the server, either one field of it or all of it. The enable switch is a separate decision and survives either way: "stop customizing this" is not "turn it on".
Releasing one field from a row written before overrides went sparse converts it first. That conversion is lossless — such a row owns every field, including the ones its JSON omits and therefore owns as empty — so the other fields stay pinned exactly where they were, and only the named one starts following the server again.
An admin-added plugin has no definition to fall back to, so resetting one is refused — deleting it is the operation that means anything there.
func (*Access) SaveChannel ¶
func (a *Access) SaveChannel(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
SaveChannel validates and persists a channel, ensures its plugin is enabled, and applies its runtime. create=true rejects an id that already exists (the create-only POST contract). It returns the reloaded channel.
func (*Access) SaveManifestPluginDefinition ¶ added in v0.63.2
func (a *Access) SaveManifestPluginDefinition(ctx context.Context, plugin manifestplugins.ManifestPlugin, fields []string) (*manifestplugins.Manifest, error)
SaveManifestPluginDefinition records the fields this request takes ownership of and leaves every other field of the row alone.
fields is the contract: a builtin's row grows exactly those keys, at the submitted values, on top of whatever it already owned. Deriving them here by diffing against the shipped definition is what this replaces — that quietly released a pinned field whenever a release happened to ship the same value.
An admin-added plugin has no definition underneath it, so its row is the whole plugin and fields does not apply; its enable state travels with it, because there is no shipped default to fall back to.
func (*Access) SearchCliToolRegistry ¶
func (a *Access) SearchCliToolRegistry(ctx context.Context, query string, limit int) ([]manifestplugins.RegistryTool, error)
SearchCliToolRegistry searches the mise tool registry so the UI can add a CLI tool by name instead of a hand-written backend key.
func (*Access) SetEmbeddingSettings ¶
func (a *Access) SetEmbeddingSettings(ctx context.Context, upd EmbeddingUpdate) (config.EmbeddingSettings, error)
SetEmbeddingSettings persists the embedding configuration. The api_key is write-only: an omitted or empty key keeps the stored one. Enabling the lane without a key is rejected (it would silently no-op).
func (*Access) SetManifestPluginEnabled ¶ added in v0.63.2
func (a *Access) SetManifestPluginEnabled(ctx context.Context, id string, enabled bool) (*manifestplugins.Manifest, error)
SetManifestPluginEnabled turns one plugin on or off. The enable switch is its own column, so this never touches the definition override: "turn this off" and "stop customizing this" stay independent decisions.
func (*Access) SetOAuthProviderConfig ¶
func (a *Access) SetOAuthProviderConfig(ctx context.Context, cfg connections.OAuthProviderConfig) (connections.OAuthProviderConfig, error)
SetOAuthProviderConfig persists an OAuth provider client configuration and returns the refreshed stored value.
func (*Access) SetVisionSettings ¶ added in v0.61.0
func (a *Access) SetVisionSettings(ctx context.Context, s config.VisionSettings) (config.VisionSettings, error)
SetVisionSettings persists the vision model. An empty model clears it, which returns image understanding to local text extraction. A non-empty model must carry its provider prefix: unlike an agent's own tiers there is no agent context to infer a default provider from, and a bare model name would resolve differently per agent.
func (*Access) SyncManifestPlugins ¶
func (a *Access) SyncManifestPlugins(ctx context.Context) (manifestplugins.ReconcileResult, error)
SyncManifestPlugins reconciles the merged manifest against the filesystem (installs binaries/skills) and returns the reconcile result.
func (*Access) TogglePlugin ¶
func (a *Access) TogglePlugin(ctx context.Context, kind, name string, enabled bool) (config.Plugin, error)
TogglePlugin enables/disables a plugin and hot-reloads its runtime.
func (*Access) UpdatePluginConfig ¶
func (a *Access) UpdatePluginConfig(ctx context.Context, kind, name string, cfg map[string]any) (config.Plugin, error)
UpdatePluginConfig validates and persists a plugin's config, then hot-reloads its runtime. Channel plugins are rejected (config lives on /channels).
func (*Access) UpdateProvider ¶
func (a *Access) UpdateProvider(ctx context.Context, id string, p config.Provider) (config.Provider, error)
UpdateProvider merges the request over the stored provider (preserving Type and defaulting Name), persists it, and hot-reloads the pool. It returns the merged provider so the transport can echo it.
type AgentChannels ¶ added in v0.63.2
type AgentChannels struct {
// contains filtered or unexported fields
}
AgentChannels is channel management confined to one caller's agents.
A channel is an agent's phone number, so the person who owns the agent owns its channels: they hold the bot token, they answer for what it says. The deployment-wide control plane stays admin-only — this reaches exactly the channels bound to agents the caller may manage, and a channel outside that set is as opaque as a missing one.
func (*AgentChannels) DeleteChannel ¶ added in v0.63.2
func (a *AgentChannels) DeleteChannel(ctx context.Context, id string) error
DeleteChannel removes one of the caller's channels.
func (*AgentChannels) GetChannel ¶ added in v0.63.2
GetChannel returns one of the caller's channels; anything else is a 404.
func (*AgentChannels) ListChannels ¶ added in v0.63.2
ListChannels returns the channels bound to the caller's agents.
func (*AgentChannels) SaveChannel ¶ added in v0.63.2
func (a *AgentChannels) SaveChannel(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
SaveChannel persists a channel that stays bound to one of the caller's agents. Both ends are checked: an update must target a channel they already own, and the saved row must still name an agent they manage — so a channel can neither be taken from another owner nor handed to an agent the caller cannot manage.
type ChannelAccess ¶ added in v0.63.2
type ChannelAccess interface {
ListChannels(ctx context.Context) ([]config.Channel, error)
GetChannel(ctx context.Context, id string) (config.Channel, error)
SaveChannel(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
DeleteChannel(ctx context.Context, id string) error
}
ChannelAccess is the channel-management surface the transport talks to. Two authorized implementations satisfy it: the admin Access, which reaches every channel in the deployment, and AgentChannels, which reaches only the channels of the agents its caller manages. The handlers are identical either way — the scope decision is made once, when the access is opened.
type ChannelBinding ¶ added in v0.62.0
type ChannelBinding struct {
// contains filtered or unexported fields
}
ChannelBinding is one authorized channel→agent binding operation.
Channel administration — credentials, naming, enablement, deletion — stays admin-only behind Begin. Choosing which agent answers on a channel is a per-user action instead, so it gets its own narrow gate here rather than widening the admin Access or letting the transport write the row directly. The operation only ever moves AgentID: the stored config (channel credentials) is carried through untouched, and it reuses the same persist + plugin-apply path as an admin save, including the (agent, platform) binding invariant.
func (*ChannelBinding) Bind ¶ added in v0.62.0
func (b *ChannelBinding) Bind(ctx context.Context, agentID string) (PublicChannel, error)
Bind points the channel at agentID, or unbinds it when agentID is empty, and returns the secret-free projection of the saved row. A bound agent must exist and be enabled; the caller's authority over that agent was already decided.
func (*ChannelBinding) CurrentAgentID ¶ added in v0.62.0
func (b *ChannelBinding) CurrentAgentID() string
CurrentAgentID is the agent this channel is bound to right now, empty when it is unbound. The caller needs it to authorize taking the channel away from its current owner.
type ChannelManagement ¶
type ChannelManagement struct {
// contains filtered or unexported fields
}
ChannelManagement is one channel-management operation. Its Save method owns the channel row plus the required channel plugin enable/apply as a single unit, so callers never need a second plugin call.
func (*ChannelManagement) Channel ¶
Channel reads the current row for this operation's channel so a caller can merge a partial update onto it. It is an admin-gated read (Begin already ran); a missing channel returns an error the caller treats as "start from a fresh row" rather than a hard failure.
func (*ChannelManagement) Save ¶
func (m *ChannelManagement) Save(ctx context.Context, ch config.Channel, cfgMap map[string]any, create bool) (config.Channel, error)
Save persists the already-authorized channel and applies its plugin as one control-plane operation. It intentionally makes no further authorization call.
func (*ChannelManagement) ValidateBinding ¶
ListChannels returns every configured channel. ValidateBinding checks the durable channel-binding invariant before an enrollment flow performs an external handshake. Save repeats the check and the database unique index closes the concurrent-write race.
type ConflictError ¶
type ConflictError struct{ Msg string }
ConflictError reports a create that collides with an existing resource, mapped to 409 by the transport. It is returned only after authorization succeeds.
func (*ConflictError) Error ¶
func (e *ConflictError) Error() string
type EmbeddingUpdate ¶
type EmbeddingUpdate struct {
Enabled bool
Model string
Dim int
BaseURL string
Normalize bool
APIKey *string
}
EmbeddingUpdate carries a validated embedding-settings write. APIKey is nil to keep the stored key (the GET never echoes it) or non-nil to replace it.
type ForbiddenError ¶
type ForbiddenError struct{ Msg string }
ForbiddenError reports a control-plane precondition that forbids an operation for a reason other than the admin gate (e.g. a resource locked by an environment override). It maps to 403 with Msg, distinct from the opaque authz.ErrForbidden denial, and is only ever returned after authorization succeeds so it never leaks to an unauthorized caller.
func (*ForbiddenError) Error ¶
func (e *ForbiddenError) Error() string
type NotFoundError ¶
type NotFoundError struct{ Msg string }
NotFoundError reports a control-plane resource that does not exist. Msg carries the historical per-resource client message so the transport preserves the 404 body byte-for-byte.
func (*NotFoundError) Error ¶
func (e *NotFoundError) Error() string
type ProviderModelItem ¶
type ProviderModelItem struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Source string `json:"source"`
Enabled bool `json:"enabled"`
Config config.ProviderModel `json:"config,omitzero"`
}
ProviderModelItem is one merged model entry for a provider: custom (config-declared) models plus fetched (cached) models, unioned. Kept here with the persistence it derives from; the transport writes it directly.
type PublicChannel ¶
type PublicChannel struct {
ID string
Type string // effective type: the configured type, or the id when unset
AgentID string
Enabled bool
}
PublicChannel is the config-secret-free projection of a channel for the user-facing channel list. It deliberately omits the raw Config JSON that config.Channel carries (channel credentials), exposing only the fields the public list renders.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service owns the control-plane resources: the persistence and runtime handles a control-plane mutation needs to apply and hot-reload changes. Authorization is the admin gate in Begin (see access.go); a nil receiver fails closed.
func NewService ¶
func NewService(store config.Store, plugins *pluginhost.Host, pools *agent.PoolManager, conns *connections.Service, log *slog.Logger) *Service
NewService builds the control-plane service from its fully-wired dependencies. The composition root constructs it once and shares the same instance behind the HTTP endpoints. log defaults to slog.Default() when nil.
func (*Service) Begin ¶
Begin authorizes one control-plane use case. The control plane is administered, not user-owned: it validates the Authority and requires IsAdmin exactly once, so a non-admin or invalid authority fails closed here, before any durable read or external action — identical to the legacy requireAdmin gate this replaces. The handler never inspects identity beyond passing the trusted Authority.
func (*Service) BeginAgentChannels ¶ added in v0.63.2
func (s *Service) BeginAgentChannels(_ context.Context, authority authz.Authority, managedAgentIDs []string) (*AgentChannels, error)
BeginAgentChannels opens agent-scoped channel management for the agents the caller was already found to manage.
The Agent decision is deliberately not made here: the caller must clear the Agent PEP for every id it passes, so the "which agents may this user manage" rule stays in the one domain that owns it — the same split BeginChannelBinding uses. An empty set is forbidden rather than an empty listing, so a user with no agents of their own cannot probe channel ids at all.
func (*Service) BeginChannelBinding ¶ added in v0.62.0
func (s *Service) BeginChannelBinding(ctx context.Context, authority authz.Authority, channelID string) (*ChannelBinding, error)
BeginChannelBinding authorizes rebinding one channel's agent for a signed-in caller and loads the target row. Any valid user principal may bind, but a non-admin may only touch an enabled channel; a disabled channel is as opaque to them as a missing one, so both fail as 404.
The Agent decisions are deliberately not made here: the caller must clear the Agent PEP for CurrentAgentID (when set) and for the agent it binds, so the "which agents may this user use" rule stays in the one domain that owns it.
func (*Service) DisabledChannelTypes ¶ added in v0.63.2
func (s *Service) DisabledChannelTypes(ctx context.Context, authority authz.Authority) (map[string]bool, error)
DisabledChannelTypes returns the set of channel-plugin names an admin has explicitly switched off, used by the transport to hide their channels.
A platform is usable unless an admin explicitly switched it off, so this reads the stored override rows rather than the merged plugin list: the builtin default for a channel plugin is "disabled" (a platform with no channel has nothing to run), and treating that default as a veto would hide every channel whose plugin row nobody flipped on. The runtime applies the same rule to instances (RuntimeHost.channelPlatformDisabled), so creating a channel needs no plugin row at all.
func (*Service) ListEnabledModels ¶
func (s *Service) ListEnabledModels(ctx context.Context, authority authz.Authority) ([]config.CachedModel, error)
ListEnabledModels returns the models available for selection: enabled models from enabled providers, plus fetched-cache models whose provider is enabled, deduplicated by provider/model and sorted. It performs no provider API calls.
func (*Service) PublicChannels ¶
func (s *Service) PublicChannels(ctx context.Context, authority authz.Authority) ([]PublicChannel, error)
PublicChannels returns the secret-free projection of every configured channel for the user-facing channel list. The transport filters visibility by the caller's agent access and the enabled plugin types; this only supplies the projected rows.
type UpstreamError ¶
type UpstreamError struct{ Err error }
UpstreamError reports a failed call to an external control-plane dependency (e.g. a provider's model-listing endpoint), mapped to 502 by the transport.
func (*UpstreamError) Error ¶
func (e *UpstreamError) Error() string
func (*UpstreamError) Unwrap ¶
func (e *UpstreamError) Unwrap() error
type ValidationError ¶
type ValidationError struct{ Msg string }
ValidationError reports a rejected control-plane input (the legacy 400 path). Msg carries the exact historical client message.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string