diff --git a/.changeset/plugin-ordering-provider-declarations.md b/.changeset/plugin-ordering-provider-declarations.md new file mode 100644 index 0000000000..d98a5da96f --- /dev/null +++ b/.changeset/plugin-ordering-provider-declarations.md @@ -0,0 +1,50 @@ +--- +"@objectstack/core": patch +"@objectstack/plugin-auth": patch +"@objectstack/plugin-email": patch +"@objectstack/plugin-hono-server": patch +"@objectstack/plugin-security": patch +"@objectstack/service-storage": patch +"@objectstack/service-settings": patch +"@objectstack/service-realtime": patch +"@objectstack/service-i18n": patch +"@objectstack/service-analytics": patch +"@objectstack/service-messaging": patch +"@objectstack/service-cluster": patch +"@objectstack/service-sms": patch +"@objectstack/service-cache": patch +"@objectstack/service-queue": patch +"@objectstack/service-job": patch +"@objectstack/service-datasource": patch +"@objectstack/service-automation": patch +"@objectstack/mcp": patch +--- + +chore(plugins,services): declare `providesServices` on the 20 remaining init-time service providers (ADR-0116 follow-up, #4131) + +ADR-0116 gave the kernel a declared ordering contract, but only +`ObjectQLPlugin` and `MetadataPlugin` had declared what their `init()` +registers. The pre-Phase-1 ordering check can only *name a provider* for +services someone declared, so its coverage was two plugins wide. + +An audit of every plugin's `init()` body (brace-matched, comments stripped, +each call classified by whether it sits inside a `try`/`if`) found 20 plugins +that register a service on every path without declaring it. All 20 now +declare `providesServices`. Purely additive: no ordering changes, no new +failure modes — a `providesServices` entry only lets the kernel say *who* +provides a service when it reports a misordering, and enriches the Phase-1 +`getService` miss diagnostic. + +Three needed a closer read before declaring, because they register the same +service from several branches (`cache`, `queue`, `job`): each early-return +branch plus the fallback registers it, so every path does — the declaration +is honest. ADR-0116's rule that a *conditionally* registered service must +never be declared is unchanged and was applied throughout. + +The same audit found 12 plugins that hard-resolve a service during `init()` +(11 of them `manifest`) without declaring `requiresServices`. None is a live +exposure — every one already declares a hard `dependencies` entry on the +provider, so the kernel orders them correctly today. Those are tracked +separately: with a hard dependency in place, `requiresServices` mostly +restates what the kernel already enforces, and its real value is on +*soft*-dependency consumers, of which `AppPlugin` is currently the only one. diff --git a/packages/core/src/api-registry-plugin.ts b/packages/core/src/api-registry-plugin.ts index 811bcc480f..58e097740d 100644 --- a/packages/core/src/api-registry-plugin.ts +++ b/packages/core/src/api-registry-plugin.ts @@ -66,6 +66,11 @@ export function createApiRegistryPlugin( return { name: 'com.objectstack.core.api-registry', + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices: ['api-registry'], type: 'standard', version: '1.0.0', diff --git a/packages/mcp/src/plugin.ts b/packages/mcp/src/plugin.ts index ad0e67aa4c..94771de790 100644 --- a/packages/mcp/src/plugin.ts +++ b/packages/mcp/src/plugin.ts @@ -93,6 +93,11 @@ export interface MCPServerPluginOptions { */ export class MCPServerPlugin implements Plugin { name = 'com.objectstack.mcp'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['mcp']; version = '1.0.0'; type = 'standard' as const; dependencies: string[] = []; diff --git a/packages/plugins/plugin-auth/src/auth-plugin.ts b/packages/plugins/plugin-auth/src/auth-plugin.ts index 933852bead..8d034ab7d3 100644 --- a/packages/plugins/plugin-auth/src/auth-plugin.ts +++ b/packages/plugins/plugin-auth/src/auth-plugin.ts @@ -137,6 +137,11 @@ export interface AuthPluginOptions extends Partial { */ export class AuthPlugin implements Plugin { name = 'com.objectstack.auth'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['auth', 'tenancy']; type = 'standard'; version = '1.0.0'; dependencies: string[] = ['com.objectstack.engine.objectql']; // manifest service required diff --git a/packages/plugins/plugin-email/src/email-plugin.ts b/packages/plugins/plugin-email/src/email-plugin.ts index a353e92333..35b749dae5 100644 --- a/packages/plugins/plugin-email/src/email-plugin.ts +++ b/packages/plugins/plugin-email/src/email-plugin.ts @@ -57,6 +57,11 @@ export interface EmailServicePluginOptions { */ export class EmailServicePlugin implements Plugin { name = 'com.objectstack.service.email'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['email']; version = '1.0.0'; type = 'standard'; dependencies = ['com.objectstack.engine.objectql']; diff --git a/packages/plugins/plugin-hono-server/src/hono-plugin.ts b/packages/plugins/plugin-hono-server/src/hono-plugin.ts index 96115bca6c..b1ba13038e 100644 --- a/packages/plugins/plugin-hono-server/src/hono-plugin.ts +++ b/packages/plugins/plugin-hono-server/src/hono-plugin.ts @@ -242,6 +242,11 @@ const DISCOVERY_ROUTE_SEGMENTS: Partial> = { */ export class HonoServerPlugin implements Plugin { name = 'com.objectstack.server.hono'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['http.server', 'http-server']; type = 'server'; version = '0.9.0'; diff --git a/packages/plugins/plugin-security/src/security-plugin.ts b/packages/plugins/plugin-security/src/security-plugin.ts index 683c7ff9ad..1bf8bd29d0 100644 --- a/packages/plugins/plugin-security/src/security-plugin.ts +++ b/packages/plugins/plugin-security/src/security-plugin.ts @@ -296,6 +296,11 @@ export { describeHighPrivilegeBits } from '@objectstack/spec/security'; export class SecurityPlugin implements Plugin { name = 'com.objectstack.security'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['security.permissions', 'security.rls', 'security.fieldMasker', 'security.bootstrapPermissionSets', 'security.fallbackPermissionSet']; type = 'standard'; version = '1.0.0'; dependencies = ['com.objectstack.engine.objectql']; diff --git a/packages/services/service-analytics/src/plugin.ts b/packages/services/service-analytics/src/plugin.ts index b44adf61cc..904cd1e4b5 100644 --- a/packages/services/service-analytics/src/plugin.ts +++ b/packages/services/service-analytics/src/plugin.ts @@ -153,6 +153,11 @@ export interface AnalyticsServicePluginOptions { */ export class AnalyticsServicePlugin implements Plugin { name = 'com.objectstack.service-analytics'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['analytics']; version = '1.0.0'; type = 'standard' as const; dependencies: string[] = []; diff --git a/packages/services/service-automation/src/plugin.ts b/packages/services/service-automation/src/plugin.ts index 383a30fd17..b3679a0414 100644 --- a/packages/services/service-automation/src/plugin.ts +++ b/packages/services/service-automation/src/plugin.ts @@ -313,6 +313,11 @@ export function findInertDeclaredConnectors( */ export class AutomationServicePlugin implements Plugin { name = 'com.objectstack.service-automation'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['automation']; version = '1.0.0'; type = 'standard' as const; // Soft dependency on metadata: we look it up at start() and tolerate absence. diff --git a/packages/services/service-cache/src/cache-service-plugin.ts b/packages/services/service-cache/src/cache-service-plugin.ts index 77b384452e..8fa8267bc5 100644 --- a/packages/services/service-cache/src/cache-service-plugin.ts +++ b/packages/services/service-cache/src/cache-service-plugin.ts @@ -61,6 +61,11 @@ export interface CacheServicePluginOptions { */ export class CacheServicePlugin implements Plugin { name = 'com.objectstack.service.cache'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['cache']; version = '1.0.0'; type = 'standard'; diff --git a/packages/services/service-cluster/src/cluster-service-plugin.ts b/packages/services/service-cluster/src/cluster-service-plugin.ts index 01714239c9..362c65e82c 100644 --- a/packages/services/service-cluster/src/cluster-service-plugin.ts +++ b/packages/services/service-cluster/src/cluster-service-plugin.ts @@ -41,6 +41,11 @@ export interface ClusterServicePluginOptions { */ export class ClusterServicePlugin implements Plugin { name = 'com.objectstack.service.cluster'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['cluster']; version = '1.0.0'; type = 'standard'; diff --git a/packages/services/service-datasource/src/datasource-admin-plugin.ts b/packages/services/service-datasource/src/datasource-admin-plugin.ts index 07e748b795..38e9fa1d7b 100644 --- a/packages/services/service-datasource/src/datasource-admin-plugin.ts +++ b/packages/services/service-datasource/src/datasource-admin-plugin.ts @@ -167,6 +167,11 @@ export interface DatasourceAdminServicePluginOptions { */ export class DatasourceAdminServicePlugin implements Plugin { name = 'com.objectstack.service-datasource-admin'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['datasource-connection', 'datasource-admin']; version = '1.0.0'; type = 'standard' as const; dependencies: string[] = []; diff --git a/packages/services/service-datasource/src/plugin.ts b/packages/services/service-datasource/src/plugin.ts index 67234ea1a2..69d24797a7 100644 --- a/packages/services/service-datasource/src/plugin.ts +++ b/packages/services/service-datasource/src/plugin.ts @@ -44,6 +44,11 @@ export interface ExternalDatasourceServicePluginOptions { */ export class ExternalDatasourceServicePlugin implements Plugin { name = 'com.objectstack.service-external-datasource'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['external-datasource']; version = '1.0.0'; type = 'standard' as const; dependencies: string[] = []; diff --git a/packages/services/service-i18n/src/i18n-service-plugin.ts b/packages/services/service-i18n/src/i18n-service-plugin.ts index c7dda632d8..9b95e34fd5 100644 --- a/packages/services/service-i18n/src/i18n-service-plugin.ts +++ b/packages/services/service-i18n/src/i18n-service-plugin.ts @@ -106,6 +106,11 @@ export interface I18nServicePluginOptions { */ export class I18nServicePlugin implements Plugin { name = 'com.objectstack.service.i18n'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['i18n']; version = '1.0.0'; type = 'standard'; diff --git a/packages/services/service-job/src/job-service-plugin.ts b/packages/services/service-job/src/job-service-plugin.ts index 705b175e27..1d7a5b54f7 100644 --- a/packages/services/service-job/src/job-service-plugin.ts +++ b/packages/services/service-job/src/job-service-plugin.ts @@ -43,6 +43,11 @@ export interface JobServicePluginOptions { */ export class JobServicePlugin implements Plugin { name = 'com.objectstack.service.job'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['job']; version = '1.1.0'; type = 'standard'; diff --git a/packages/services/service-messaging/src/messaging-service-plugin.ts b/packages/services/service-messaging/src/messaging-service-plugin.ts index da0488c3a1..0acc5c5d16 100644 --- a/packages/services/service-messaging/src/messaging-service-plugin.ts +++ b/packages/services/service-messaging/src/messaging-service-plugin.ts @@ -71,6 +71,11 @@ export interface MessagingServicePluginOptions { */ export class MessagingServicePlugin implements Plugin { name = 'com.objectstack.service.messaging'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['messaging', 'notification']; version = '1.0.0'; type = 'standard' as const; dependencies = ['com.objectstack.engine.objectql']; diff --git a/packages/services/service-queue/src/queue-service-plugin.ts b/packages/services/service-queue/src/queue-service-plugin.ts index 8d6913a1d2..626ff5baf0 100644 --- a/packages/services/service-queue/src/queue-service-plugin.ts +++ b/packages/services/service-queue/src/queue-service-plugin.ts @@ -36,6 +36,11 @@ export interface QueueServicePluginOptions { */ export class QueueServicePlugin implements Plugin { name = 'com.objectstack.service.queue'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['queue']; version = '1.1.0'; type = 'standard'; diff --git a/packages/services/service-realtime/src/realtime-service-plugin.ts b/packages/services/service-realtime/src/realtime-service-plugin.ts index c9b00c839e..b9b8040baf 100644 --- a/packages/services/service-realtime/src/realtime-service-plugin.ts +++ b/packages/services/service-realtime/src/realtime-service-plugin.ts @@ -45,6 +45,11 @@ export interface RealtimeServicePluginOptions { */ export class RealtimeServicePlugin implements Plugin { name = 'com.objectstack.service.realtime'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['realtime']; version = '1.0.0'; type = 'standard'; dependencies = ['com.objectstack.engine.objectql']; diff --git a/packages/services/service-settings/src/settings-service-plugin.ts b/packages/services/service-settings/src/settings-service-plugin.ts index 7096714936..ae793801d4 100644 --- a/packages/services/service-settings/src/settings-service-plugin.ts +++ b/packages/services/service-settings/src/settings-service-plugin.ts @@ -73,6 +73,11 @@ export interface SettingsServicePluginOptions { */ export class SettingsServicePlugin implements Plugin { name = SETTINGS_PLUGIN_ID; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['settings']; version = SETTINGS_PLUGIN_VERSION; type = 'standard' as const; diff --git a/packages/services/service-sms/src/sms-plugin.ts b/packages/services/service-sms/src/sms-plugin.ts index c1b85aac88..ed069a5d72 100644 --- a/packages/services/service-sms/src/sms-plugin.ts +++ b/packages/services/service-sms/src/sms-plugin.ts @@ -87,6 +87,11 @@ function providerFromSettings(values: Record): { */ export class SmsServicePlugin implements Plugin { name = 'com.objectstack.service.sms'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['sms']; version = '1.0.0'; type = 'standard' as const; diff --git a/packages/services/service-storage/src/storage-service-plugin.ts b/packages/services/service-storage/src/storage-service-plugin.ts index 4b810e1e55..0b0952496a 100644 --- a/packages/services/service-storage/src/storage-service-plugin.ts +++ b/packages/services/service-storage/src/storage-service-plugin.ts @@ -115,6 +115,11 @@ export interface StorageServicePluginOptions { */ export class StorageServicePlugin implements Plugin { name = 'com.objectstack.service.storage'; + /** + * Services init() registers on every path (ADR-0116, #4131) — lets the + * kernel name this plugin when a consumer requires one before it inits. + */ + providesServices = ['file-storage']; version = '1.0.0'; type = 'standard';