Documentation
¶
Overview ¶
Package plugin owns publication and the catalog lifecycle for the private Marketplace.
The server's half of publishing is deliberately independent of the client's: it hashes, extracts, and inspects the bytes it received rather than trusting what the publisher said about them. A client that validated is a convenience; this is the check that decides what a deployment's members can install.
Index ¶
- Variables
- func SelectRelease(releases []coreplugin.Release, opts SelectOptions) (*coreplugin.Release, error)
- type ActivateInput
- type ActivationStore
- type CatalogStore
- type CreateEntryInput
- type PackageStore
- type PublishInput
- type SelectOptions
- type Service
- func (s *Service) Activate(ctx context.Context, in ActivateInput) (*coreplugin.Activation, error)
- func (s *Service) CreateEntry(ctx context.Context, in CreateEntryInput) (*coreplugin.Plugin, error)
- func (s *Service) GetEntry(ctx context.Context, name string) (*coreplugin.Plugin, error)
- func (s *Service) GetRelease(ctx context.Context, name, version string) (*coreplugin.Release, error)
- func (s *Service) ListActivations(ctx context.Context, spaceID string) ([]coreplugin.Activation, error)
- func (s *Service) ListEntries(ctx context.Context, includeArchived bool) ([]coreplugin.Plugin, error)
- func (s *Service) ListReleases(ctx context.Context, name string) ([]coreplugin.Release, error)
- func (s *Service) MovePin(ctx context.Context, in ActivateInput) (*coreplugin.Activation, error)
- func (s *Service) OpenPackage(ctx context.Context, release coreplugin.Release) (io.ReadCloser, int64, error)
- func (s *Service) Publish(ctx context.Context, in PublishInput) (*coreplugin.Release, error)
- func (s *Service) ResolveSelection(ctx context.Context, spaceID string, names []string, actorID string) ([]coreplugin.Activation, error)
- func (s *Service) SetActivationEnabled(ctx context.Context, spaceID, pluginName string, enabled bool, actorID string) (*coreplugin.Activation, error)
- func (s *Service) SetArchived(ctx context.Context, name string, archived bool, actorID string) error
- func (s *Service) SetCuration(ctx context.Context, spaceID string, mode coreplugin.Curation, actorID string) error
- func (s *Service) UpdateEntry(ctx context.Context, name string, in coreplugin.UpdateInput, actorID string) (*coreplugin.Plugin, error)
- func (s *Service) Yank(ctx context.Context, name, version, actorID, reason string) error
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoRelease means nothing published fits, which is different from the // plugin not existing. ErrNoRelease = errors.New("no release matches") // ErrReleaseYanked means the exact version asked for was withdrawn. Saying // so is the point: a recovery is allowed, but not by accident. ErrReleaseYanked = errors.New("that release was withdrawn") // ErrClientTooOld means the release states a lower bound this build does // not meet. ErrClientTooOld = errors.New("this build is older than the release requires") )
Reasons a release cannot be handed over.
var ErrExecutableContent = errors.New("this release contributes hooks or MCP servers, which cannot be activated for background runs yet")
ErrExecutableContent means the release contributes hooks or MCP servers.
Phase D1 refuses those unconditionally. Phase D2 replaces this with the operator's unattended-eligibility flag, which is the check that decides whether a program may run where nobody is present; until that flag exists there is nothing to check it against, and activating anyway would put a space's runs past a gate the deployment has not built.
var ErrInvalidCuration = errors.New("unknown plugin curation mode")
ErrInvalidCuration means the requested curation mode is not one of the two.
var ErrInvalidPackage = errors.New("invalid plugin package")
ErrInvalidPackage means the uploaded bytes are not a plugin this deployment would be able to load. It is a refusal of the request, not a server fault.
var ErrNameMismatch = errors.New("the package names a different plugin")
ErrNameMismatch means the manifest inside the package names a different plugin than the route it was published to.
var ErrNoActivatableRelease = errors.New("this plugin has no release that can be activated")
ErrNoActivatableRelease means the catalog has the plugin but nothing this space could be pinned to — every release is yanked, a prerelease, or refused for the reason ErrExecutableContent gives.
var ErrNotActivated = errors.New("this space has not activated this plugin")
ErrNotActivated means an agent named a plugin its space has not activated and the space curates its own list. In an open-mode space the same name activates the plugin instead; see docs/design/plugin-space-distribution.md §4.1.
Functions ¶
func SelectRelease ¶
func SelectRelease(releases []coreplugin.Release, opts SelectOptions) (*coreplugin.Release, error)
SelectRelease picks the release a caller should install.
The default is the newest release that is not a prerelease, not withdrawn, and whose lower bound this build meets. Every one of those exclusions is recoverable by naming a version exactly, which is what keeps the default safe without making it a wall.
Types ¶
type ActivateInput ¶
ActivateInput pins a release for a space. Version empty means the newest activatable release, which is what a curated activation from Portal sends when the admin did not pick one.
type ActivationStore ¶
type ActivationStore interface {
ActivatePlugin(ctx context.Context, in coreplugin.ActivateInput) (*coreplugin.Activation, error)
GetPluginActivation(ctx context.Context, spaceID, pluginName string) (*coreplugin.Activation, error)
ListPluginActivations(ctx context.Context, spaceID string) ([]coreplugin.Activation, error)
MovePluginActivationPin(ctx context.Context, in coreplugin.MovePinInput) (*coreplugin.Activation, error)
SetPluginActivationEnabled(ctx context.Context, spaceID, pluginName string, enabled bool, actorID string) (*coreplugin.Activation, error)
}
ActivationStore is the persistence capability used to curate and resolve the plugin releases available to a space.
type CatalogStore ¶
type CatalogStore interface {
CreatePlugin(ctx context.Context, in coreplugin.CreateInput) (*coreplugin.Plugin, error)
GetPlugin(ctx context.Context, name string) (*coreplugin.Plugin, error)
ListPlugins(ctx context.Context, includeArchived bool) ([]coreplugin.Plugin, error)
UpdatePlugin(ctx context.Context, name string, in coreplugin.UpdateInput) (*coreplugin.Plugin, error)
SetPluginArchived(ctx context.Context, name string, archived bool) error
CreatePluginRelease(ctx context.Context, in coreplugin.CreateReleaseInput) (*coreplugin.Release, error)
GetPluginRelease(ctx context.Context, name, version string) (*coreplugin.Release, error)
ListPluginReleases(ctx context.Context, name string) ([]coreplugin.Release, error)
YankPluginRelease(ctx context.Context, name, version, actor, reason string) error
}
CatalogStore is the persistence capability used by Marketplace catalog orchestration. The service owns this port because it owns the transaction sequence and selection rules that consume it.
type CreateEntryInput ¶
CreateEntryInput reserves a catalog name.
type PackageStore ¶
type PackageStore interface {
// PackageKey returns the object key one release's bytes are stored under.
//
// The layout belongs to the implementation; what this service does with the
// result is persist it on the release record and hand it back to Open. It
// also validates: a plugin name that is not one path segment, or a digest
// that is not a lower-case sha256, has no key.
PackageKey(prefix, pluginName, digest string) (string, error)
// Put stores bytes under key. Keys are content-addressed, so writing one
// twice writes the same bytes and a partial write is never visible.
Put(ctx context.Context, key string, r io.Reader) error
// Open returns the object and its size. A missing object is reported as the
// storage layer's not-found error, which the plugin HTTP handler maps.
Open(ctx context.Context, key string) (io.ReadCloser, int64, error)
// Exists reports whether the bytes are already stored, so a republish of
// identical content does not have to upload them again.
Exists(ctx context.Context, key string) (bool, error)
}
PackageStore holds the immutable bytes of published releases.
This service declares the contract it needs; an object-store adapter satisfies it structurally, so the storage package never has to know a service exists. Only the two implementations under internal/infra/objectstore and the in-memory test double implement it.
It is deliberately not artifact storage. A package belongs to the deployment's catalog rather than to a space's run, so it must not inherit space artifact authorization or retention — a catalog entry that vanished with a space's retention window could no longer explain an installation that is still on somebody's machine.
Every method streams. A package is bounded, but bounded at tens of megabytes, and a server that read one into memory per request would be sized by its largest plugin rather than by its traffic.
type PublishInput ¶
type PublishInput struct {
// PluginName is the route's name. The manifest inside the package has to
// agree with it.
PluginName string
// Body is the archive, streamed rather than held.
Body io.Reader
// Source is the publisher's claim about the checkout the bytes came from.
// The server cannot verify it, so it is recorded as a claim beside a digest
// the server calculated itself.
Source coreplugin.ReleaseSource
ActorID string
}
PublishInput is one upload.
type SelectOptions ¶
type SelectOptions struct {
// Version asks for one exact release. Empty takes the default selection.
Version string
// ClientVersion is the build that will install it. An empty or unplaceable
// one satisfies every bound, because refusing to install on a version
// nobody can compare would break every contributor's checkout.
ClientVersion string
// AllowYanked permits a withdrawn release. It exists so a recovery has to
// say out loud what it is doing.
AllowYanked bool
// AllowPrerelease permits a prerelease in the default selection. Naming a
// prerelease version exactly always works without it.
AllowPrerelease bool
}
SelectOptions describes which release a caller wants.
type Service ¶
type Service struct {
Catalog CatalogStore
// Activations and Spaces carry the space half of distribution: which
// releases a space's background runs may use, and who fills that list.
// They are nil in a deployment that only publishes and installs locally.
Activations ActivationStore
Spaces corespace.Store
Packages PackageStore
// KeyPrefix scopes package keys inside the object store.
KeyPrefix string
Audit *audit.Recorder
// Limits bound what one upload may cost. The zero value takes the defaults.
Limits archive.Limits
}
Service publishes releases and manages catalog entries.
func (*Service) Activate ¶
func (s *Service) Activate(ctx context.Context, in ActivateInput) (*coreplugin.Activation, error)
Activate pins a release for a space's background runs.
It is the curated path: a person chose this plugin. The open-mode path is ResolveSelection, which activates as a side effect of an agent naming it and records that difference in the row's origin.
func (*Service) CreateEntry ¶
func (s *Service) CreateEntry(ctx context.Context, in CreateEntryInput) (*coreplugin.Plugin, error)
CreateEntry adds a catalog entry.
func (*Service) GetRelease ¶
func (s *Service) GetRelease(ctx context.Context, name, version string) (*coreplugin.Release, error)
GetRelease returns one release, or (nil, nil) when there is none.
func (*Service) ListActivations ¶
func (s *Service) ListActivations(ctx context.Context, spaceID string) ([]coreplugin.Activation, error)
ListActivations returns a space's activations, suspended ones included.
func (*Service) ListEntries ¶
func (s *Service) ListEntries(ctx context.Context, includeArchived bool) ([]coreplugin.Plugin, error)
ListEntries returns the catalog. Archived entries are included only when asked for: hiding a retired entry from the person who retired it would leave no way to restore it.
func (*Service) ListReleases ¶
ListReleases returns every release of one plugin, yanked ones included: which to install needs the version arithmetic, and an exact version can still be recovered by someone who acknowledges the state.
func (*Service) MovePin ¶
func (s *Service) MovePin(ctx context.Context, in ActivateInput) (*coreplugin.Activation, error)
MovePin repoints a space's activation at another release.
It is separate from Activate because it is the action a capability report is read before: the bytes change, so what the space accepted changes with them. The new release passes the same content check a first activation does, which is what stops a plugin whose next version adds a hook from arriving as an update.
func (*Service) OpenPackage ¶
func (s *Service) OpenPackage(ctx context.Context, release coreplugin.Release) (io.ReadCloser, int64, error)
OpenPackage streams one release's bytes.
The stream is handed to the caller rather than read here: a download that buffered a package would size the server by its largest plugin.
func (*Service) Publish ¶
func (s *Service) Publish(ctx context.Context, in PublishInput) (*coreplugin.Release, error)
Publish stores one release.
The order matters. Bytes are stored before the release row, so a failure between the two leaves an orphan at a content-addressed key rather than a row pointing at nothing. An orphan costs disk; a dangling row costs an install.
func (*Service) ResolveSelection ¶
func (s *Service) ResolveSelection(ctx context.Context, spaceID string, names []string, actorID string) ([]coreplugin.Activation, error)
ResolveSelection turns the plugin names an agent definition carries into the activations that back them, activating what the space's mode allows.
It is the one seam the agent write path calls, because the answer to "may this agent name this plugin" is the space's curation mode and nothing the caller can work out for itself. In curated mode an unactivated name is refused; in open mode it activates the newest activatable release and attributes that to the person saving the agent.
A suspended activation is returned rather than refused: the write is not where that fails. A run resolving the same name is (§5.3), so refusing here would stop somebody editing an agent to remove the plugin that is failing it.
func (*Service) SetActivationEnabled ¶
func (s *Service) SetActivationEnabled(ctx context.Context, spaceID, pluginName string, enabled bool, actorID string) (*coreplugin.Activation, error)
SetActivationEnabled suspends or resumes an activation without losing the pin. Suspending fails the runs of the agents that name the plugin; that is intended, and it is why this is not a delete.
func (*Service) SetArchived ¶
func (s *Service) SetArchived(ctx context.Context, name string, archived bool, actorID string) error
SetArchived retires or restores a catalog entry.
It hides the entry and refuses new releases. It deletes nothing: a copy somebody already installed keeps working, and the record still explains where that copy came from.
func (*Service) SetCuration ¶
func (s *Service) SetCuration(ctx context.Context, spaceID string, mode coreplugin.Curation, actorID string) error
SetCuration records who fills a space's plugin activation list.
func (*Service) UpdateEntry ¶
func (s *Service) UpdateEntry(ctx context.Context, name string, in coreplugin.UpdateInput, actorID string) (*coreplugin.Plugin, error)
UpdateEntry changes display metadata. The name is not editable: it identifies the plugin every installed copy came from.