Documentation
¶
Overview ¶
Package plugin provides a minimal plugin system for cleat.
Plugins are Go packages compiled into the worker binary. They register via init() using Register(), and the worker discovers them at startup. The registry, lifecycle management, host function helpers, and crash recovery wrappers live here.
Key types:
- Plugin — interface all plugins implement
- PluginInfo — metadata for discovery and documentation
- Environment — infrastructure access (DB, HTTP mux, logger)
- Registry — central plugin function registry
- Manifest — plugin manifest for code generation
Package plugin provides a minimal plugin system for cleat.
Plugins are Go packages compiled into the worker binary. They register themselves via init() and the central registry. The worker discovers them at startup, calls Init(), and optionally calls lifecycle methods based on which optional interfaces the plugin implements.
Design principle: give plugins access to infrastructure through purpose-built interfaces. The Environment struct provides a PluginDB interface (not *sql.DB directly), along with standard library types (*http.ServeMux, *slog.Logger).
Crash recovery boundaries:
Go-compiled plugins share the worker process, so a panic in a plugin host function can crash the entire worker. The RecoverPluginFunc and RecoverPluginStreamFunc wrappers (in recovery.go) add defer/recover boundaries around each host function call. When a panic is caught, the plugin is marked unhealthy and subsequent invocations are rejected without calling into the plugin.
Long-term migration: compile plugins to WASM modules instead of linking them into the worker binary. WASM provides process-level isolation so a plugin crash cannot affect the worker or other plugins. The recovery wrappers exist for Go-compiled plugins only.
Index ¶
- Constants
- Variables
- func DownloadWASM(ctx context.Context, url string) ([]byte, error)
- func InitAll(ctx context.Context, env *Environment, plugins []*LoadedPlugin)
- func LimitClause(placeholder string, d Dialect) string
- func MustRegisterOfficialPublicKey(keyID, pubKeyHex string)
- func QuoteIdent(name string, d Dialect) string
- func Rebind(query string, d Dialect) string
- func Register(info PluginInfo, ctor func() Plugin)
- func RegisterOfficialPublicKey(keyID, pubKeyHex string) error
- func RegisterPluginTables(ctx context.Context, db *sql.DB, pluginName string, tableNames []string) error
- func RunMigrations(ctx context.Context, db *sql.DB, dialect Dialect, coreMigrations []Migration, ...) error
- func ValidateCapabilities(declared, limits CapabilityLimits) error
- func ValidateManifest(m *Manifest) error
- func VerifyChecksum(data []byte, expectedChecksum string) error
- func VerifyManifestSignature(m *Manifest, allowUnsigned bool) error
- func VerifyWasmSignature(wasmBytes []byte, iv *IndexVersion, pluginName string, allowUnsigned bool) error
- func WithCallContext(ctx context.Context, cc *CallContext) context.Context
- type AuditLogger
- type CallContext
- type Capabilities
- type CapabilityLimits
- type Command
- type DatabaseAccess
- type Dialect
- type Environment
- type FieldDef
- type FuncOptions
- type FuncRegistry
- type HasBackground
- type HasCommands
- type HasHealth
- type HasHostFunctions
- type HasMiddleware
- type HasMigrations
- type HasRoutes
- type HealthStatus
- type HostFuncDef
- type IndexEntry
- type IndexVersion
- type JSONColumn
- type LoadedPlugin
- type Manifest
- type Migration
- type PanicError
- type Plugin
- type PluginDB
- type PluginFunc
- type PluginHealthTracker
- func (t *PluginHealthTracker) IsHealthy(pluginName string) bool
- func (t *PluginHealthTracker) MarkHealthy(pluginName string)
- func (t *PluginHealthTracker) MarkUnhealthy(pluginName string, err error)
- func (t *PluginHealthTracker) UnhealthyError(pluginName string) error
- func (t *PluginHealthTracker) UnhealthyStatus() []HealthStatus
- type PluginIndex
- type PluginInfo
- type PluginStreamFunc
- type PluginTx
- type Query
- type RowScanner
- type Rows
- type Secret
- type ServeMux
- type Stoppable
- type StreamEvent
- type StreamFuncRegistry
- type TenantPools
- type TypeDef
Constants ¶
const RedactedPlaceholder = "[redacted]"
RedactedPlaceholder is what a Secret marshals and prints as.
Variables ¶
var DefaultOfficialPublicKey = ""
DefaultOfficialPublicKey is the default Ed25519 public key for verifying official cleat plugin signatures. This is a well-known key distributed with the cleat binary. In production, operators can override this with a custom key via configuration.
The hex-encoded value below is a placeholder and MUST be replaced with the actual official cleat signing public key before release.
var OfficialPublicKeys = map[string]ed25519.PublicKey{}
OfficialPublicKeys maps SigningKeyID to Ed25519 public keys for official plugin verification. Keys are registered at build time or loaded from configuration.
Functions ¶
func DownloadWASM ¶
DownloadWASM downloads a WASM binary from the given URL.
func InitAll ¶
func InitAll(ctx context.Context, env *Environment, plugins []*LoadedPlugin)
InitAll calls Init on each loaded plugin in order. Plugins that panic or return an error during Init are marked unhealthy but do not halt initialization of remaining plugins.
Each plugin receives an Environment whose DB field is restricted based on the plugin's declared DatabaseAccess: None gets nil, ReadOnly gets nil (caller must set appropriate read-only adapter), ReadWrite gets the raw env (caller must set a read-write adapter).
NOTE: This function is primarily used by tests. In production, the worker sets per-plugin DB wrappers itself.
func LimitClause ¶
LimitClause returns a row-limiting clause using the given placeholder.
SQL Server has no LIMIT. It uses OFFSET/FETCH, which is only valid after an ORDER BY -- so callers must already be ordering their results, which any query with a row limit should be doing anyway to be deterministic.
func MustRegisterOfficialPublicKey ¶
func MustRegisterOfficialPublicKey(keyID, pubKeyHex string)
MustRegisterOfficialPublicKey is like RegisterOfficialPublicKey but panics on error. Use in init() functions or main() to register well-known keys.
func QuoteIdent ¶
QuoteIdent quotes a SQL identifier for the given dialect.
It exists because `key` and `value` -- both natural column names, and both used by shipped plugins -- are reserved words in MySQL and SQL Server. An unquoted `key` produced
Error 1064 (42000): You have an error in your SQL syntax ... mssql: Incorrect syntax near the keyword 'key'.
on every kvstore and feature-flags route, on both backends. Quoting is per-dialect: PostgreSQL and standard SQL use double quotes, MySQL uses backticks, SQL Server uses square brackets.
Embedded quote characters are doubled/escaped so that a caller cannot inject through an identifier, but identifiers should still come from constants rather than user input.
func Rebind ¶
Rebind translates PostgreSQL $N parameter placeholders to the dialect-appropriate form. It also replaces now() with SYSUTCDATETIME() for MSSQL. PostgreSQL placeholders ($1, $2, ...) are left as-is.
func Register ¶
func Register(info PluginInfo, ctor func() Plugin)
Register registers a plugin constructor with its PluginInfo. Call from init().
func RegisterOfficialPublicKey ¶
RegisterOfficialPublicKey registers an Ed25519 public key for verifying official plugin signatures. The keyID is an opaque identifier (e.g., "cleat-official-2026") and the pubKeyHex is a hex-encoded Ed25519 public key (32 bytes = 64 hex chars).
func RegisterPluginTables ¶
func RegisterPluginTables(ctx context.Context, db *sql.DB, pluginName string, tableNames []string) error
RegisterPluginTables inserts entries into admin.plugin_tables so that the tenant provisioning system knows which tables to GRANT. Called during plugin Init after migrations run.
func RunMigrations ¶
func RunMigrations(ctx context.Context, db *sql.DB, dialect Dialect, coreMigrations []Migration, plugins []*LoadedPlugin) error
RunMigrations runs core migrations and plugin migrations in order. Core migrations are run first, then plugins in dependency order. Each plugin's migrations are tracked in a plugin_migrations table so they run only once.
func ValidateCapabilities ¶
func ValidateCapabilities(declared, limits CapabilityLimits) error
ValidateCapabilities checks whether declared capabilities are within the granted limits. Returns an error listing ALL violations, not just the first.
func ValidateManifest ¶
ValidateManifest validates a manifest against programmatic rules. Returns nil if valid, or an error describing all validation failures.
func VerifyChecksum ¶
VerifyChecksum checks that the given data matches the expected SHA-256 checksum. If expectedChecksum is empty, verification is skipped.
func VerifyManifestSignature ¶
VerifyManifestSignature checks the Ed25519 signature on a manifest. If the manifest has no signature field, verification is skipped (returns nil). For official plugins (those using a cleat/ prefix or having no slash in the name), a missing signature is an error unless allowUnsigned is true.
func VerifyWasmSignature ¶
func VerifyWasmSignature(wasmBytes []byte, iv *IndexVersion, pluginName string, allowUnsigned bool) error
VerifyWasmSignature checks the Ed25519 signature of a WASM binary against the expected checksum and the index version's signature field. The signature is computed over the SHA-256 hash of the WASM binary bytes.
func WithCallContext ¶
func WithCallContext(ctx context.Context, cc *CallContext) context.Context
WithCallContext injects call context into the context.
Types ¶
type AuditLogger ¶
type AuditLogger interface {
// Deploy records a plugin deployment event.
Deploy(ctx context.Context, pluginName, pluginVersion string) error
// Deprecate records a plugin deprecation event.
Deprecate(ctx context.Context, pluginName, pluginVersion string) error
// CapabilityChange records a change in plugin capabilities.
CapabilityChange(ctx context.Context, pluginName, pluginVersion, details string) error
// Invocation records a plugin function invocation. The dropRate
// (0.0-1.0) controls sampling: 0.995 means ~1 in 200 are logged.
Invocation(ctx context.Context, pluginName, functionName string, dropRate float64) error
// EnforceRetention deletes audit log entries that exceed the policy.
EnforceRetention(ctx context.Context, policy any) (int64, error)
}
AuditLogger is the interface for recording plugin lifecycle events. The concrete implementation writes to the plugin_audit_log table.
type CallContext ¶
type CallContext struct {
TenantID string `json:"tenant_id"`
WorkflowID string `json:"workflow_id"`
DB *sql.DB // tenant-scoped database connection
}
CallContext carries per-invocation metadata injected by the engine before calling plugin host functions.
func CallContextFromContext ¶
func CallContextFromContext(ctx context.Context) *CallContext
CallContextFromContext extracts call context from the context. Returns nil if not present.
type Capabilities ¶
type Capabilities struct {
Database bool `json:"database" yaml:"database"`
StartWorkflow bool `json:"start_workflow" yaml:"start_workflow"`
SignalWorkflow bool `json:"signal_workflow" yaml:"signal_workflow"`
HTTPRoutes bool `json:"http_routes" yaml:"http_routes"`
HTTPMiddleware bool `json:"http_middleware" yaml:"http_middleware"`
BackgroundWorker bool `json:"background_worker" yaml:"background_worker"`
CallPlugin []string `json:"call_plugin" yaml:"call_plugin"`
}
Capabilities declares what infrastructure access a plugin needs.
func DefaultCapabilities ¶
func DefaultCapabilities() Capabilities
DefaultCapabilities returns a Capabilities struct with all fields false.
type CapabilityLimits ¶
type CapabilityLimits struct {
Database DatabaseAccess `json:"database"`
StartWorkflow bool `json:"start_workflow"`
SignalWorkflow bool `json:"signal_workflow"`
HTTPRoutes bool `json:"http_routes"`
HTTPMiddleware bool `json:"http_middleware"`
BackgroundWorker bool `json:"background_worker"`
CallPlugin []string `json:"call_plugin"` // empty = deny all, ["*"] = allow all
}
CapabilityLimits defines the maximum capabilities allowed for a class of plugins. Used by operators to restrict what third-party plugins can do.
func DefaultLimits ¶
func DefaultLimits() CapabilityLimits
DefaultLimits returns the default CapabilityLimits for community plugins. By default, community plugins get NO database access, and signal_workflow, but NOT start_workflow or http_routes.
func DeriveCapabilities ¶
func DeriveCapabilities(p Plugin) CapabilityLimits
DeriveCapabilities derives CapabilityLimits from the optional interfaces a Go compile-time plugin implements. Called by the loader to determine what capabilities a Go plugin has based on which interfaces it satisfies.
func (CapabilityLimits) IsSet ¶
func (l CapabilityLimits) IsSet() bool
IsSet returns true if any capability limit is configured (non-default). Used to distinguish intentionally-set limits from a zero-value struct.
type DatabaseAccess ¶
type DatabaseAccess string
DatabaseAccess represents the level of database access a plugin is granted.
const ( DatabaseAccessNone DatabaseAccess = "none" DatabaseAccessReadOnly DatabaseAccess = "read_only" DatabaseAccessReadWrite DatabaseAccess = "read_write" )
type Environment ¶
type Environment struct {
DB PluginDB
Mux ServeMux // *http.ServeMux on host, interface{} on TinyGo
Config []byte
Logger *slog.Logger
TenantID string
Done <-chan struct{}
Dialect Dialect
// StartWorkflow starts a new workflow instance using the latest deployed version.
// Plugins use this to trigger workflow executions (e.g., from cron schedules
// or job queues). Returns the run ID of the new workflow instance.
StartWorkflow func(ctx context.Context, defName string, input json.RawMessage) (runID string, err error)
// SignalWorkflow delivers a signal to a running workflow instance.
// The signal name and JSON payload are recorded deterministically
// in the workflow_signals table.
SignalWorkflow func(ctx context.Context, workflowID, signalName, payload string) error
// Audit provides access to the plugin audit log. Plugins can record
// deployment, deprecation, capability changes, and invocation events.
// May be nil if the audit log is not configured.
Audit *AuditLogger
}
Environment provides plugins with access to cleat infrastructure.
type FieldDef ¶
type FieldDef struct {
Type string `json:"type" yaml:"type"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Optional bool `json:"optional,omitempty" yaml:"optional,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Items *FieldDef `json:"items,omitempty" yaml:"items,omitempty"`
Values []string `json:"values,omitempty" yaml:"values,omitempty"`
Fields map[string]FieldDef `json:"fields,omitempty" yaml:"fields,omitempty"`
KeyType *FieldDef `json:"key_type,omitempty" yaml:"key_type,omitempty"`
ValueType *FieldDef `json:"value_type,omitempty" yaml:"value_type,omitempty"`
}
FieldDef describes a field in a type definition.
type FuncOptions ¶
type FuncOptions struct {
Name string // function name (required)
Idempotent bool // if true, safe to re-invoke during replay
}
FuncOptions configures a registered host function.
type FuncRegistry ¶
type FuncRegistry interface {
// Register adds a host function. The engine handles WASM I/O,
// event history recording, and deterministic replay.
Register(opts FuncOptions, fn PluginFunc) error
}
FuncRegistry lets plugins register workflow-callable functions. The plugin name is implicit -- each plugin gets its own scoped registry.
type HasBackground ¶
HasBackground: plugin runs a background goroutine.
type HasCommands ¶
HasCommands: plugin adds CLI subcommands.
type HasHostFunctions ¶
type HasHostFunctions interface {
Plugin
RegisterHostFunctions(scope FuncRegistry) error
}
HasHostFunctions: plugin adds functions callable from workflows. These functions are automatically recorded in event history and replayed deterministically -- plugin authors don't need to handle replay.
type HasMiddleware ¶
HasMiddleware: plugin wraps the HTTP handler chain.
type HasMigrations ¶
HasMigrations: plugin needs database tables.
type HealthStatus ¶
type HealthStatus struct {
Name string `json:"name"`
Healthy bool `json:"healthy"`
Error string `json:"error,omitempty"`
}
HealthStatus describes the runtime health of a single plugin.
type HostFuncDef ¶
type HostFuncDef struct {
Description string `json:"description" yaml:"description"`
Input TypeDef `json:"input" yaml:"input"`
Output TypeDef `json:"output" yaml:"output"`
Idempotent bool `json:"idempotent,omitempty" yaml:"idempotent,omitempty"`
Streaming bool `json:"streaming,omitempty" yaml:"streaming,omitempty"`
}
HostFuncDef describes a host function in a plugin manifest.
type IndexEntry ¶
type IndexEntry struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Author string `yaml:"author"`
Repository string `yaml:"repository,omitempty"`
Versions []IndexVersion `yaml:"versions"`
}
IndexEntry represents a plugin entry in the index.
func (*IndexEntry) IsOfficial ¶
func (e *IndexEntry) IsOfficial() bool
IsOfficial returns true if the plugin is a cleat official plugin. Official plugins use the "cleat/" prefix or have no slash in the name.
type IndexVersion ¶
type IndexVersion struct {
Version string `yaml:"version"`
WasmURL string `yaml:"wasm_url"`
ManifestURL string `yaml:"manifest_url,omitempty"`
Checksum string `yaml:"checksum"`
MinCleatVersion string `yaml:"min_cleat_version,omitempty"`
Bundled bool `yaml:"bundled,omitempty"`
Description string `yaml:"description,omitempty"`
// Signature is an optional Ed25519 hex-encoded signature of the WASM
// binary SHA-256 checksum. When present and the plugin is official,
// the signature is verified during deployment. The signing key is
// identified by SigningKeyID.
Signature string `yaml:"signature,omitempty"`
// SigningKeyID identifies the Ed25519 public key used to produce
// Signature. Example: "cleat-official-2026".
SigningKeyID string `yaml:"signing_key_id,omitempty"`
}
IndexVersion represents a specific version in the index.
type JSONColumn ¶
type JSONColumn struct {
Raw json.RawMessage
}
JSONColumn scans a JSON-valued column into json.RawMessage regardless of whether the driver delivers it as []byte or as string.
database/sql will convert a driver string into *[]byte, but not into a *json.RawMessage: json.RawMessage is a named []byte type and the conversion is not in convertAssign's fast path. lib/pq returns jsonb as []byte, so scanning straight into json.RawMessage works on PostgreSQL and hides the problem; go-mssqldb returns NVARCHAR as string, and every read of a JSON column failed with
sql: Scan error on column index 0, name "value": unsupported Scan, storing driver.Value type string into type *json.RawMessage
That surfaced as HTTP 500 on single-row reads and as silently empty lists, because the row-scan error was logged and the row skipped.
func (JSONColumn) Value ¶
func (j JSONColumn) Value() (driver.Value, error)
Value implements driver.Valuer so the same type can be used for writes.
It yields a string, not []byte, and that is deliberate. go-mssqldb maps a []byte argument to VARBINARY; inserting that into the NVARCHAR column that backs a JSON value stores the binary representation, which reads back as text that is not valid JSON. The symptom is a 200 with an empty body, because encoding/json fails part-way through writing the response. A string argument is sent as NVARCHAR by go-mssqldb, and lib/pq and go-sql-driver both accept a string for jsonb/JSON columns, so one form is correct everywhere.
type LoadedPlugin ¶
LoadedPlugin wraps a plugin instance with its current state.
func Discover ¶
func Discover() ([]*LoadedPlugin, error)
Discover instantiates all registered plugins in dependency order without calling Init. The caller should call RunMigrations followed by InitAll to complete plugin initialization.
func LoadAll ¶
func LoadAll(ctx context.Context, env *Environment) ([]*LoadedPlugin, error)
LoadAll instantiates all registered plugins in dependency order, calls Init on each, and returns the successfully loaded plugins. A plugin that panics during Init is disabled and reported.
type Manifest ¶
type Manifest struct {
Name string `json:"name" yaml:"name"`
Version string `json:"version" yaml:"version"`
Description string `json:"description" yaml:"description"`
Author string `json:"author" yaml:"author"`
Repository string `json:"repository,omitempty" yaml:"repository,omitempty"`
MinCleatVersion string `json:"min_cleat_version,omitempty" yaml:"min_cleat_version,omitempty"`
Capabilities Capabilities `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
HostFunctions map[string]HostFuncDef `json:"host_functions,omitempty" yaml:"host_functions,omitempty"`
Types map[string]TypeDef `json:"types,omitempty" yaml:"types,omitempty"`
// Signature is an optional Ed25519 signature of the canonical JSON
// representation of the manifest (excluding the signature field itself).
// When present, the runtime verifies the signature against the plugin
// author's public key before deploying. Initially this is required only
// for official plugins (cleat/ prefix or no slash in name); community
// plugins may opt in.
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
// SigningKeyID identifies the public key used to produce Signature.
// This is an opaque identifier (e.g., "cleat-official-2026") that the
// runtime uses to look up the corresponding Ed25519 public key.
SigningKeyID string `json:"signing_key_id,omitempty" yaml:"signing_key_id,omitempty"`
}
Manifest is the parsed form of a plugin.json or plugin.yaml file.
func LoadManifest ¶
LoadManifest reads and parses a plugin manifest from a file path. Supports .json and .yaml/.yml extensions. Support for YAML requires gopkg.in/yaml.v3 or equivalent; currently only JSON is supported natively.
type Migration ¶
type Migration struct {
Version int
Up string // required — SQL for PostgreSQL (the default)
UpMySQL string // optional — MySQL DDL. Empty means PG-only for this version.
UpMSSQL string // optional — MSSQL DDL. Empty means PG-only for this version.
Down string // optional — SQL to roll back
}
Migration describes a single database migration.
Up is the default SQL (PostgreSQL) and is required. UpMySQL and UpMSSQL are optional dialect-specific overrides. If the active dialect is MySQL or MSSQL and the corresponding field is empty, the migration is skipped with a warning.
type PanicError ¶
type PanicError struct {
Plugin string `json:"plugin"`
Value any `json:"value"`
Stack string `json:"stack"`
}
PanicError is returned when a plugin host function panics. It captures the panic value and the full goroutine stack trace so operators can diagnose the root cause.
Long-term, plugins should be compiled to WASM modules for true process-level isolation. See design docs at docs/wasm-migration.md.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
func (*PanicError) Unwrap ¶
func (e *PanicError) Unwrap() error
Unwrap returns nil — PanicError is a terminal error, not a wrapper.
type Plugin ¶
type Plugin interface {
Info() PluginInfo
Init(ctx context.Context, env *Environment) error
}
Plugin is the only required interface. Every plugin must implement this.
type PluginDB ¶
type PluginDB interface {
Begin(ctx context.Context) (PluginTx, error)
Exec(ctx context.Context, query string, args ...any) (int64, error)
Query(ctx context.Context, query string, args ...any) (Rows, error)
QueryRow(ctx context.Context, query string, args ...any) RowScanner
Ping(ctx context.Context) error
}
PluginDB is the database handle available to plugins. It intentionally does not mirror *sql.DB — plugins get a scoped interface appropriate to their declared DatabaseAccess level.
type PluginFunc ¶
PluginFunc is a plugin host function implementation. Takes JSON input, returns JSON output.
func RecoverPluginFunc ¶
func RecoverPluginFunc(pluginName string, tracker *PluginHealthTracker, fn PluginFunc) PluginFunc
RecoverPluginFunc wraps a PluginFunc with panic recovery. If the wrapped function panics, the panic is caught, the plugin is marked unhealthy via the provided tracker, and a PanicError is returned. The full stack trace is logged using the standard log package.
type PluginHealthTracker ¶
type PluginHealthTracker struct {
// contains filtered or unexported fields
}
PluginHealthTracker tracks the runtime health of Go-compiled plugins. When a plugin's host function panics, it is marked unhealthy and all subsequent invocations are blocked without calling into the plugin.
Migration note: WASM-compiled plugins provide process-level isolation and do not need this tracker because a WASM crash cannot take down the worker. The tracker exists for Go-compiled plugins only.
func NewPluginHealthTracker ¶
func NewPluginHealthTracker() *PluginHealthTracker
NewPluginHealthTracker creates a new PluginHealthTracker.
func (*PluginHealthTracker) IsHealthy ¶
func (t *PluginHealthTracker) IsHealthy(pluginName string) bool
IsHealthy reports whether the plugin is healthy.
func (*PluginHealthTracker) MarkHealthy ¶
func (t *PluginHealthTracker) MarkHealthy(pluginName string)
MarkHealthy clears any previous unhealthy status for the given plugin.
func (*PluginHealthTracker) MarkUnhealthy ¶
func (t *PluginHealthTracker) MarkUnhealthy(pluginName string, err error)
MarkUnhealthy marks a plugin as unhealthy with the given fatal error. Once marked, all future invocations of the plugin's host functions will return this error without executing the function.
func (*PluginHealthTracker) UnhealthyError ¶
func (t *PluginHealthTracker) UnhealthyError(pluginName string) error
UnhealthyError returns the error that caused the plugin to be marked unhealthy, or nil if the plugin is healthy.
func (*PluginHealthTracker) UnhealthyStatus ¶
func (t *PluginHealthTracker) UnhealthyStatus() []HealthStatus
UnhealthyStatus returns the current health status of all plugins that have been marked unhealthy. Healthy plugins are not included because the tracker only records failures — it does not maintain a registry of all plugin names.
type PluginIndex ¶
type PluginIndex struct {
Plugins []IndexEntry `yaml:"plugins"`
}
PluginIndex represents the parsed index.yaml file.
func FetchIndex ¶
func FetchIndex(ctx context.Context, urlStr string) (*PluginIndex, error)
FetchIndex downloads and parses the plugin index from a URL or file path. Supports http://, https:// URLs and local file paths (absolute or relative).
func (*PluginIndex) Resolve ¶
func (idx *PluginIndex) Resolve(name, constraint string) (*IndexEntry, *IndexVersion, error)
Resolve finds the best matching version for a plugin name and constraint. Constraint can be empty (latest), "latest", or a semver constraint (^1.0.0, ~1.2.0, >=1.0.0, =1.0.0, or bare 1.0.0 treated as exact match).
type PluginInfo ¶
type PluginInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Author string `json:"author,omitempty"`
Requires []string `json:"requires,omitempty"`
DatabaseAccess DatabaseAccess `json:"database_access,omitempty"`
}
PluginInfo describes a plugin for discovery and documentation.
type PluginStreamFunc ¶
type PluginStreamFunc func(ctx context.Context, inputJSON string) (<-chan StreamEvent, error)
PluginStreamFunc is a plugin host function that returns a stream of events. Takes JSON input and returns a channel that receives stream events.
func RecoverPluginStreamFunc ¶
func RecoverPluginStreamFunc(pluginName string, tracker *PluginHealthTracker, fn PluginStreamFunc) PluginStreamFunc
RecoverPluginStreamFunc wraps a PluginStreamFunc with panic recovery. If the wrapped function panics during setup (before returning the channel), the panic is caught and handled like RecoverPluginFunc. Panics during channel consumption are not caught here — the consumer must handle them.
type PluginTx ¶
type PluginTx interface {
Exec(ctx context.Context, query string, args ...any) (int64, error)
Query(ctx context.Context, query string, args ...any) (Rows, error)
QueryRow(ctx context.Context, query string, args ...any) RowScanner
Commit() error
Rollback() error
}
PluginTx is a transaction scoped to a plugin operation.
type Query ¶
type Query struct {
Default string // required — PostgreSQL
MySQL string // optional
MSSQL string // optional
}
Query holds dialect-specific variants of a runtime SQL query. Default (PostgreSQL) is required; MySQL and MSSQL are optional overrides.
type RowScanner ¶
RowScanner abstracts a single row result for single-row queries.
type Rows ¶
type Rows interface {
RowScanner
Next() bool
Close() error
Err() error
}
Rows is the result of a multi-row query.
type Secret ¶
type Secret string
Secret is a string that refuses to leave the process.
It exists because five plugins independently stored a credential in a plain string field and returned it, unredacted, from their list and get endpoints: webhookingest and notifications (HMAC signing keys), pagerdutyalert (routing key), slacknotify (webhook URL, which IS the credential), and datadogexport (API key). Any bug that let a caller read one tenant's configuration row therefore disclosed the credential itself rather than merely its existence.
The obvious fix is a redaction helper called on the way out. That was rejected: a helper you have to remember is a helper the next plugin author forgets, and the failure is silent and invisible in review -- the response looks fine because the field is populated. datadogexport had exactly that shape and it did not hold; its redactAPIKey() was defeated by adding ?show_api_key=true to the URL, with no authorization check behind it.
So the redaction is in the TYPE. A Secret always marshals as RedactedPlaceholder, whatever the caller does, and there is no code path through encoding/json that emits the real value. Forgetting to redact now requires not using the type, which TestPluginSecretsUseTheSecretType catches.
It stays usable at both ends:
UnmarshalJSON accepts a real value, because create and update requests carry one. Scan and Value carry the real value to and from the database, because that is where it legitimately lives. Reveal returns the real value for the code that must actually use it -- signing an HMAC, calling an API. It is a method rather than a conversion so that every intentional use is greppable.
String and Format redact too, so a Secret cannot leak through a log line or a %v in an error either. That is not hypothetical: the value most likely to end up in an error message is the one that failed to work.
func (Secret) Format ¶
Format redacts for every verb, including %v and %#v.
String alone is not enough: %#v ignores Stringer and prints the underlying value, and %q would too.
func (Secret) MarshalJSON ¶
MarshalJSON always emits the placeholder. This is the whole point of the type; there is deliberately no option to disable it.
func (Secret) Reveal ¶
Reveal returns the real value.
A method rather than a plain conversion so that every intentional use is one grep away. A reviewer asking "where does this credential actually get used" gets an answer from `grep -rn '.Reveal()'`.
func (Secret) String ¶
String redacts, so a Secret cannot leak through fmt, a log line, or an error.
func (*Secret) UnmarshalJSON ¶
UnmarshalJSON accepts a real value, because create and update requests carry one.
It rejects the placeholder. Without that, a client that read a config, edited one unrelated field and PUT the whole object back would silently overwrite the stored credential with the literal string "[redacted]" -- a round-trip-shaped data loss that is easy to build a UI on top of by accident. The caller is told to omit the field instead.
type ServeMux ¶
ServeMux is *http.ServeMux on the host, or interface{} in TinyGo WASM builds where net/http is unavailable.
type StreamEvent ¶
StreamEvent represents a single chunk of a streaming response.
type StreamFuncRegistry ¶
type StreamFuncRegistry interface {
RegisterStream(opts FuncOptions, fn PluginStreamFunc) error
}
StreamFuncRegistry lets plugins register streaming host functions.
type TenantPools ¶
type TenantPools struct {
// Owner pool for administrative operations (claiming, migrations).
OwnerDB *sql.DB
// contains filtered or unexported fields
}
TenantPools manages per-tenant database connection pools. Each pool connects directly as the tenant's login role, so there is no SET ROLE / RESET ROLE to escape — the connection IS the tenant.
func NewTenantPools ¶
func NewTenantPools(ownerDB *sql.DB, baseDSN string, maxConns int) *TenantPools
NewTenantPools creates a TenantPools manager. maxConns is the max open connections per tenant pool (0 = default 25). baseDSN is a connection string template like: "host=localhost port=5432 dbname=cleat sslmode=disable" The user and password are added per tenant.