Skip to main content
Version: 0.5.x

Introduction​

services.Plugins() returns the plugin governance domain capability namespace. Source plugins access it through services.Plugins(), while dynamic plugins declare service: plugins in plugin.yaml and access it through the pluginbridge.Default().Plugins() client. It is not a single method collection but aggregates multiple plugin governance sub-capabilities:

Sub-CapabilityDescription
Registry()Reads plugin governance views, such as plugin versions, install status, enable state, and tenant-controllable plugin lists
Config()Reads the current plugin's own static configuration
State()Queries plugin enable state, authoritative enable state, and capability provider status
Lifecycle()Orchestrates tenant-level plugin disable and tenant deletion pre-checks and post-notifications

Plugins() also implements registry read methods, so callers can directly use services.Plugins().BatchGetPlugins and services.Plugins().ListTenantPlugins to read plugin views, or explicitly express dependency intent through Registry().

Capability Phase: Runtime

Supported Plugin Types: Source plugins, Dynamic plugins

Capability Design​

Sub-Capability System​

Registry Sub-Capability​

The registry capability is for reading plugin governance views and does not expose the host sys_plugin table or runtime internal cache state. Plugin views typically contain plugin ID, version, install status, enable state, and lifecycle status. Tenant views additionally contain name, type, description, install mode, scope nature, and tenant-level enable state.

Config Sub-Capability​

Plugins().Config() reads the current plugin's own static configuration. Its responsibility differs from HostConfig():

CapabilityRead Scope
Plugins().Config()Current plugin-scoped config.yaml
HostConfig()Host config values; dynamic plugins must declare authorized keys

The plugin configuration read priority is:

PrioritySourceDescription
1plugin.<plugin-id> section in host config.yamlDeployment-level unified override, exclusive priority
2plugins/<plugin-id>/config.yaml under production config rootOps override, production priority
3Development-time manifest/config/config.yamlSource plugin development default config
4manifest/config/config.yaml bound to dynamic plugin artifactPublished artifact default config

When a plugin.<plugin-id> section exists in the host config.yaml, that section is the sole source for that plugin's config — file-level config no longer participates in resolution. See Plugin Business Configuration for details.

manifest/config/config.example.yaml is only a config template and does not participate in runtime default reads.

State Sub-Capability​

Plugins().State() provides three query semantics, each serving different consistency and performance needs:

MethodData Source and SemanticsUse Case
IsEnabledReads process-local enable cache state, preserving tenant, request scope, and runtime gatesHigh-frequency checks like menu filtering, route visibility, permission filtering
IsEnabledAuthoritativeBypasses local cache state, forces reading persisted plugin governance stateGlobal middleware, write protection, controls that need immediate response to admin state changes
IsProviderEnabledChecks whether the plugin is platform-enabled and can serve as a framework capability providerPre-check before AI, Org, Tenant capability provider calls

Lifecycle Sub-Capability​

Plugins().Lifecycle() targets governance modules like tenant management and plugin management. It is used to ask registered plugins whether an operation may proceed before a governance action, and to notify plugins to execute cleanup or observation logic after the operation completes.

It differs from pluginhost.SourcePlugin.Lifecycle():

EntryRole
pluginhost.SourcePlugin.Lifecycle()A single source plugin registers its own install, upgrade, disable, uninstall, tenant disable, tenant delete, and install mode change callbacks
services.Plugins().Lifecycle()Governance modules orchestrate cross-plugin tenant-level pre-checks and post-notifications

Interface Definitions​

Registry Sub-Capability Interface​

MethodDescription
BatchGetPluginsBatch-reads visible plugin views; missing results don't reveal specific reasons
ListTenantPluginsReads current tenant-controllable plugin list, including global enable state and tenant enable state
RegistryReturns the same registry read interface, convenient for dependency injection to expose only registry capability

Config Sub-Capability Interface​

MethodDescription
GetReturns the raw config value
ExistsChecks whether a config key exists
ScanScans a config section into the target struct
StringReads a string value, returning default when missing or blank
BoolReads a boolean value, returning default when missing
IntReads an integer value, returning default when missing
DurationReads a duration value, returning default when missing or blank

State Sub-Capability Interface​

MethodDescription
IsEnabledReads process-local enable cache state, suitable for high-frequency checks
IsEnabledAuthoritativeForces reading persisted state, suitable for global control
IsProviderEnabledChecks whether capability provider is available

Lifecycle Sub-Capability Interface​

MethodDescription
EnsureTenantPluginDisableAllowedPre-check before tenant disables a plugin; any plugin rejection blocks the operation
NotifyTenantPluginDisabledBest-effort notification after tenant disables a plugin
EnsureTenantDeleteAllowedPre-check before tenant deletion
NotifyTenantDeletedBest-effort notification after tenant deletion

Management Command Interface​

EntryMethodDescription
Admin().Plugins()SetPluginEnabledChanges plugin enable state, with tenant, lifecycle, and state machine checks
Admin().Plugins()ProvisionTenantDefaultsFills default plugin supply state for a specified tenant

Dynamic Plugin Interface​

Dynamic MethodDescription
plugins.batch_getBatch-reads visible plugin views
plugins.tenant.listReads current tenant-controllable plugin list
plugins.enabled.checkChecks whether a plugin is enabled
plugins.provider_enabled.checkChecks whether capability provider is available
plugins.enabled_authoritative.checkForces reading persisted enable state
config.getReads current plugin-scoped config
lifecycle.tenant_plugin_disable.ensureTenant plugin disable pre-check
lifecycle.tenant_plugin_disabled.notifyTenant plugin disable post-notification
lifecycle.tenant_delete.ensureTenant deletion pre-check
lifecycle.tenant_deleted.notifyTenant deletion post-notification

Dynamic plugin lifecycles are orchestrated through the above lifecycle methods in the bridge contract.

Capability Usage​

Source Plugin Usage​

Source plugins access sub-capabilities through services.Plugins(), explicitly passing the domain-required CapabilityContext when reading plugin views:

// Read plugin views
result, err := services.Plugins().BatchGetPlugins(ctx, capabilityCtx, pluginIDs)

// Read current tenant-controllable plugin list
tenantPlugins, err := services.Plugins().ListTenantPlugins(ctx, capabilityCtx)

// Read plugin config
endpoint, err := services.Plugins().Config().String(ctx, "api.endpoint", "")

// High-frequency check if plugin is enabled
if !services.Plugins().State().IsEnabled(ctx, pluginID) {
return errors.New("plugin not enabled")
}

// Global control uses authoritative read
if !services.Plugins().State().IsEnabledAuthoritative(ctx, pluginID) {
return errors.New("plugin disabled")
}

// Capability provider check
if services.Plugins().State().IsProviderEnabled(ctx, "linapro-ai-core") {
// Use AI capability
}

Trusted source plugins executing management commands:

err := services.Admin().Plugins().SetPluginEnabled(ctx, capabilityCtx, pluginID, true)
err := services.Admin().Plugins().ProvisionTenantDefaults(ctx, capabilityCtx, tenantID)

Dynamic Plugin Usage​

Dynamic plugins declare plugin governance capabilities through hostServices.plugins:

hostServices:
- service: plugins
methods:
- config.get
- plugins.batch_get
- plugins.enabled.check

Dynamic plugin lifecycles are managed through bridge contracts, including callback registration for install, upgrade, disable, and uninstall phases. Usage on the dynamic plugin side:

// Read plugin config
value, err := pluginbridge.Default().Plugins().Config().String(ctx, "api.endpoint", "")

Design Constraints​

  • High-frequency checks prefer IsEnabled. It is designed for menu, route, and permission filtering, avoiding frequent access to persisted state.
  • Global control uses IsEnabledAuthoritative. When stale cache state would cause security or governance errors, use the authoritative read.
  • Provider state is independent of business entry visibility. Some plugin business entries may be invisible to the current tenant but still available as platform capability providers.
  • Ensure* can block. When lifecycle pre-checks return errors, governance operations should not proceed.
  • Notify* is best-effort. Post-notifications don't return errors; plugin callback failures should only be logged, not roll back completed governance operations.
  • Dynamic plugin lifecycles go through bridge contracts. Dynamic plugin artifact lifecycle contracts are in pluginbridge/contract and are not exposed as standard callable services through dynamic hostServices.