Documentation
¶
Overview ¶
Package pluginsource persists ORG-PRIVATE plugin bindings: "this team's runs get the plugin living in this git repository".
Why it exists. ADR-079 made an enabled plugin's skills reach a cloud runner pod, but it resolves them from the LAUNCHING instance's iterion home — and a cloud server pod's home is ephemeral too. An operator who installs a private plugin there loses it on the next restart, silently: the mirror is best-effort, so runs simply proceed without the skill and produce a plausible-looking wrong result. There is also no way to scope a plugin to one org: enablement is a global per-instance toggle.
A PluginSource fixes both by moving the AUTHORITY off the pod's filesystem: the durable record (in Mongo, cloud state) says which git repo holds the plugin and which stored secret reads it. The checkout is only ever a re-derivable cache. Team-scoped, so each org brings its own private plugins.
The credential is referenced, never inlined: SecretID points at a GenericSecret (a PAT or deploy key) the fetcher consumes without the value passing through this package.
Index ¶
- Constants
- Variables
- func ValidName(name string) error
- type Fetcher
- type File
- type MemoryStore
- func (m *MemoryStore) Create(_ context.Context, s PluginSource) error
- func (m *MemoryStore) Delete(_ context.Context, id string) error
- func (m *MemoryStore) Get(_ context.Context, id string) (PluginSource, error)
- func (m *MemoryStore) ListByTenant(_ context.Context, tenantID string) ([]PluginSource, error)
- func (m *MemoryStore) ListEnabledByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
- func (m *MemoryStore) Update(_ context.Context, s PluginSource) error
- type MongoStore
- func (s *MongoStore) Create(ctx context.Context, ps PluginSource) error
- func (s *MongoStore) Delete(ctx context.Context, id string) error
- func (s *MongoStore) EnsureSchema(ctx context.Context) error
- func (s *MongoStore) Get(ctx context.Context, id string) (PluginSource, error)
- func (s *MongoStore) ListByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
- func (s *MongoStore) ListEnabledByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
- func (s *MongoStore) Update(ctx context.Context, ps PluginSource) error
- type PluginSource
- type Resolver
- type Store
Constants ¶
const CollectionName = "plugin_sources"
CollectionName is the Mongo collection backing plugin sources.
This collection is the whole point of the package: it is the DURABLE authority a cloud pod's ephemeral filesystem cannot be. A restarted server re-derives its plugin checkouts from here instead of silently losing them.
const FetchTimeout = 90 * time.Second
FetchTimeout bounds a single git operation. A plugin fetch sits on the launch path, so a hung remote must fail fast and loudly rather than stall a run submission indefinitely.
Variables ¶
Functions ¶
Types ¶
type Fetcher ¶
type Fetcher struct {
// CacheDir roots the checkouts. Ephemeral by design: it is a cache, never
// the authority — the durable record is the PluginSource in the store, so
// a cold pod simply re-derives.
CacheDir string
// CredentialFor resolves a source's read credential. It returns the
// secret VALUE, which the fetcher passes to git without logging it.
// Nil (or a nil return) means "public repository".
CredentialFor func(ctx context.Context, s PluginSource) (string, error)
}
Fetcher materialises a PluginSource's repository into a local cache directory and returns the checkout path.
Caching is keyed by (git_url, ref) and, once resolved, the tree is left in place: with a PINNED ref the content is immutable, so the second launch and every one after it cost nothing. That is the whole reason the design prefers pinning over a moving branch — it collapses "resolve at launch" into a no-network operation without introducing a staleness window.
type File ¶
type File struct {
// Kind is the .claude/ leaf dir: "skills" | "commands" | "agents".
Kind string
Name string
Content []byte
}
File is one markdown contribution read out of a git-hosted plugin, in the shape the run-contribution payload expects.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-process Store for tests and local mode.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
func (*MemoryStore) Create ¶
func (m *MemoryStore) Create(_ context.Context, s PluginSource) error
func (*MemoryStore) Get ¶
func (m *MemoryStore) Get(_ context.Context, id string) (PluginSource, error)
func (*MemoryStore) ListByTenant ¶
func (m *MemoryStore) ListByTenant(_ context.Context, tenantID string) ([]PluginSource, error)
func (*MemoryStore) ListEnabledByTenant ¶
func (m *MemoryStore) ListEnabledByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
func (*MemoryStore) Update ¶
func (m *MemoryStore) Update(_ context.Context, s PluginSource) error
type MongoStore ¶
type MongoStore struct {
// contains filtered or unexported fields
}
MongoStore is the cloud-mode Store.
func NewMongoStore ¶
func NewMongoStore(db *mongo.Database) *MongoStore
func (*MongoStore) Create ¶
func (s *MongoStore) Create(ctx context.Context, ps PluginSource) error
func (*MongoStore) EnsureSchema ¶
func (s *MongoStore) EnsureSchema(ctx context.Context) error
func (*MongoStore) Get ¶
func (s *MongoStore) Get(ctx context.Context, id string) (PluginSource, error)
func (*MongoStore) ListByTenant ¶
func (s *MongoStore) ListByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
func (*MongoStore) ListEnabledByTenant ¶
func (s *MongoStore) ListEnabledByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
func (*MongoStore) Update ¶
func (s *MongoStore) Update(ctx context.Context, ps PluginSource) error
type PluginSource ¶
type PluginSource struct {
ID string `bson:"_id" json:"id"`
TenantID string `bson:"tenant_id" json:"tenant_id"`
// Name is the plugin name as it will appear in the registry. It must
// match the plugin.yaml `name` when the repo carries a manifest; for a
// bare skills/ repo it names the synthesized skills-only plugin.
Name string `bson:"name" json:"name"`
// GitURL is the clone URL (https or ssh, per the credential kind).
GitURL string `bson:"git_url" json:"git_url"`
// Ref is what to check out. PIN A TAG OR SHA in production: a moving
// branch turns every launch into a network round-trip AND makes the
// skill change under the operator's feet, which is the silent-drift
// failure this whole area exists to prevent. A pinned ref makes the
// cache immutable and updates an explicit, auditable act.
Ref string `bson:"ref" json:"ref"`
// SecretID references the GenericSecret holding the read credential
// (PAT or deploy key). Empty for a public repository.
SecretID string `bson:"secret_id,omitempty" json:"secret_id,omitempty"`
// Enabled gates whether this source contributes to the team's runs.
Enabled bool `bson:"enabled" json:"enabled"`
CreatedBy string `bson:"created_by" json:"created_by"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
UpdatedAt time.Time `bson:"updated_at" json:"updated_at"`
}
PluginSource binds a team to a plugin hosted in a git repository.
func (*PluginSource) PinnedRef ¶
func (s *PluginSource) PinnedRef() bool
PinnedRef reports whether Ref looks like an immutable pin (a full sha or a tag) rather than a moving branch. Callers use it to warn: a moving ref is allowed but means the plugin can change under a run without any operator action.
func (*PluginSource) Validate ¶
func (s *PluginSource) Validate() error
Validate checks a source before it is persisted. Errors are explicit — a malformed source must fail at write time, not silently contribute nothing at launch time.
type Resolver ¶
type Resolver struct {
Store Store
Fetcher *Fetcher
// Warnf reports a source that could not be resolved. Optional.
Warnf func(format string, args ...any)
}
Resolver turns a team's enabled PluginSources into contribution files.
This is what makes an org-private plugin DURABLE and TEAM-SCOPED: the authority is the store record (Mongo), not a pod's filesystem, so a restart re-derives instead of silently losing the plugin — and each team resolves only its own sources.
func (*Resolver) Resolve ¶
Resolve returns the contribution files for every enabled source of a tenant.
Failure policy is deliberate and differs from the local plugin registry's "best-effort, skip quietly": a source the operator explicitly bound and enabled, which then fails to fetch, is reported as an ERROR. Silently contributing nothing is precisely the failure mode that lets a deploy run without its platform playbook and still report success.
type Store ¶
type Store interface {
Create(ctx context.Context, s PluginSource) error
Get(ctx context.Context, id string) (PluginSource, error)
Update(ctx context.Context, s PluginSource) error
Delete(ctx context.Context, id string) error
// ListEnabledByTenant returns the sources a launch must resolve.
ListEnabledByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
ListByTenant(ctx context.Context, tenantID string) ([]PluginSource, error)
}
Store persists plugin sources. Mongo-backed in cloud, memory in tests.