From 175c5bc4591852a2d5d8aa6b882184f390877ece Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 02:49:27 +0000 Subject: [PATCH 1/6] Initial plan From cb897f3eb750cb5223c946a7e526daca0eadda27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 02:56:19 +0000 Subject: [PATCH 2/6] Add Zod schemas for new contract data structures Created comprehensive Zod schemas for all data structures in the new contracts added by PR #422: - plugin-validator.zod.ts: ValidationResult, PluginMetadata - startup-orchestrator.zod.ts: StartupOptions, HealthStatus, PluginStartupResult - plugin-lifecycle-events.zod.ts: Event payload schemas and types - service-registry.zod.ts: ServiceMetadata, registry configuration All schemas include: - Full JSDoc documentation with examples - Comprehensive tests (53 test cases, all passing) - Runtime validation support - JSON Schema generation capability - Default values where appropriate - Proper TypeScript type inference Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/system/index.mdx | 4 + content/docs/references/system/meta.json | 4 + .../spec/json-schema/system/EventPhase.json | 15 + .../spec/json-schema/system/HealthStatus.json | 33 ++ .../system/HookRegisteredEvent.json | 30 ++ .../system/HookTriggeredEvent.json | 34 ++ .../json-schema/system/KernelEventBase.json | 19 + .../json-schema/system/KernelReadyEvent.json | 29 ++ .../system/KernelShutdownEvent.json | 23 ++ .../json-schema/system/PluginErrorEvent.json | 46 +++ .../json-schema/system/PluginEventBase.json | 24 ++ .../system/PluginLifecycleEventType.json | 31 ++ .../system/PluginLifecyclePhaseEvent.json | 38 ++ .../json-schema/system/PluginMetadata.json | 37 ++ .../system/PluginRegisteredEvent.json | 28 ++ .../system/PluginStartupResult.json | 73 ++++ .../spec/json-schema/system/ScopeConfig.json | 28 ++ .../spec/json-schema/system/ScopeInfo.json | 39 ++ .../system/ServiceFactoryRegistration.json | 44 +++ .../json-schema/system/ServiceMetadata.json | 43 +++ .../system/ServiceRegisteredEvent.json | 28 ++ .../system/ServiceRegistryConfig.json | 39 ++ .../json-schema/system/ServiceScopeType.json | 15 + .../system/ServiceUnregisteredEvent.json | 24 ++ .../json-schema/system/StartupOptions.json | 36 ++ .../system/StartupOrchestrationResult.json | 104 ++++++ .../json-schema/system/ValidationError.json | 28 ++ .../json-schema/system/ValidationResult.json | 71 ++++ .../json-schema/system/ValidationWarning.json | 28 ++ packages/spec/src/system/index.ts | 4 + .../system/plugin-lifecycle-events.test.ts | 225 ++++++++++++ .../src/system/plugin-lifecycle-events.zod.ts | 336 ++++++++++++++++++ .../spec/src/system/plugin-validator.test.ts | 136 +++++++ .../spec/src/system/plugin-validator.zod.ts | 158 ++++++++ .../spec/src/system/service-registry.test.ts | 188 ++++++++++ .../spec/src/system/service-registry.zod.ts | 256 +++++++++++++ .../src/system/startup-orchestrator.test.ts | 181 ++++++++++ .../src/system/startup-orchestrator.zod.ts | 200 +++++++++++ 38 files changed, 2679 insertions(+) create mode 100644 packages/spec/json-schema/system/EventPhase.json create mode 100644 packages/spec/json-schema/system/HealthStatus.json create mode 100644 packages/spec/json-schema/system/HookRegisteredEvent.json create mode 100644 packages/spec/json-schema/system/HookTriggeredEvent.json create mode 100644 packages/spec/json-schema/system/KernelEventBase.json create mode 100644 packages/spec/json-schema/system/KernelReadyEvent.json create mode 100644 packages/spec/json-schema/system/KernelShutdownEvent.json create mode 100644 packages/spec/json-schema/system/PluginErrorEvent.json create mode 100644 packages/spec/json-schema/system/PluginEventBase.json create mode 100644 packages/spec/json-schema/system/PluginLifecycleEventType.json create mode 100644 packages/spec/json-schema/system/PluginLifecyclePhaseEvent.json create mode 100644 packages/spec/json-schema/system/PluginMetadata.json create mode 100644 packages/spec/json-schema/system/PluginRegisteredEvent.json create mode 100644 packages/spec/json-schema/system/PluginStartupResult.json create mode 100644 packages/spec/json-schema/system/ScopeConfig.json create mode 100644 packages/spec/json-schema/system/ScopeInfo.json create mode 100644 packages/spec/json-schema/system/ServiceFactoryRegistration.json create mode 100644 packages/spec/json-schema/system/ServiceMetadata.json create mode 100644 packages/spec/json-schema/system/ServiceRegisteredEvent.json create mode 100644 packages/spec/json-schema/system/ServiceRegistryConfig.json create mode 100644 packages/spec/json-schema/system/ServiceScopeType.json create mode 100644 packages/spec/json-schema/system/ServiceUnregisteredEvent.json create mode 100644 packages/spec/json-schema/system/StartupOptions.json create mode 100644 packages/spec/json-schema/system/StartupOrchestrationResult.json create mode 100644 packages/spec/json-schema/system/ValidationError.json create mode 100644 packages/spec/json-schema/system/ValidationResult.json create mode 100644 packages/spec/json-schema/system/ValidationWarning.json create mode 100644 packages/spec/src/system/plugin-lifecycle-events.test.ts create mode 100644 packages/spec/src/system/plugin-lifecycle-events.zod.ts create mode 100644 packages/spec/src/system/plugin-validator.test.ts create mode 100644 packages/spec/src/system/plugin-validator.zod.ts create mode 100644 packages/spec/src/system/service-registry.test.ts create mode 100644 packages/spec/src/system/service-registry.zod.ts create mode 100644 packages/spec/src/system/startup-orchestrator.test.ts create mode 100644 packages/spec/src/system/startup-orchestrator.zod.ts diff --git a/content/docs/references/system/index.mdx b/content/docs/references/system/index.mdx index e01b15ca31..ce719f6aca 100644 --- a/content/docs/references/system/index.mdx +++ b/content/docs/references/system/index.mdx @@ -29,7 +29,11 @@ This section contains all protocol schemas for the system layer of ObjectStack. + + + + diff --git a/content/docs/references/system/meta.json b/content/docs/references/system/meta.json index 34b00cff1e..3c2db17627 100644 --- a/content/docs/references/system/meta.json +++ b/content/docs/references/system/meta.json @@ -22,7 +22,11 @@ "object-storage", "plugin", "plugin-capability", + "plugin-lifecycle-events", + "plugin-validator", "search-engine", + "service-registry", + "startup-orchestrator", "tracing", "translation" ] diff --git a/packages/spec/json-schema/system/EventPhase.json b/packages/spec/json-schema/system/EventPhase.json new file mode 100644 index 0000000000..98e65c45f2 --- /dev/null +++ b/packages/spec/json-schema/system/EventPhase.json @@ -0,0 +1,15 @@ +{ + "$ref": "#/definitions/EventPhase", + "definitions": { + "EventPhase": { + "type": "string", + "enum": [ + "init", + "start", + "destroy" + ], + "description": "Plugin lifecycle phase" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/HealthStatus.json b/packages/spec/json-schema/system/HealthStatus.json new file mode 100644 index 0000000000..0bdade7870 --- /dev/null +++ b/packages/spec/json-schema/system/HealthStatus.json @@ -0,0 +1,33 @@ +{ + "$ref": "#/definitions/HealthStatus", + "definitions": { + "HealthStatus": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean", + "description": "Whether the plugin is healthy" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when health check was performed" + }, + "details": { + "type": "object", + "additionalProperties": {}, + "description": "Optional plugin-specific health details" + }, + "message": { + "type": "string", + "description": "Error message if plugin is unhealthy" + } + }, + "required": [ + "healthy", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/HookRegisteredEvent.json b/packages/spec/json-schema/system/HookRegisteredEvent.json new file mode 100644 index 0000000000..5bb10bf574 --- /dev/null +++ b/packages/spec/json-schema/system/HookRegisteredEvent.json @@ -0,0 +1,30 @@ +{ + "$ref": "#/definitions/HookRegisteredEvent", + "definitions": { + "HookRegisteredEvent": { + "type": "object", + "properties": { + "hookName": { + "type": "string", + "description": "Name of the hook" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + }, + "handlerCount": { + "type": "integer", + "minimum": 0, + "description": "Number of handlers registered for this hook" + } + }, + "required": [ + "hookName", + "timestamp", + "handlerCount" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/HookTriggeredEvent.json b/packages/spec/json-schema/system/HookTriggeredEvent.json new file mode 100644 index 0000000000..be45ddc719 --- /dev/null +++ b/packages/spec/json-schema/system/HookTriggeredEvent.json @@ -0,0 +1,34 @@ +{ + "$ref": "#/definitions/HookTriggeredEvent", + "definitions": { + "HookTriggeredEvent": { + "type": "object", + "properties": { + "hookName": { + "type": "string", + "description": "Name of the hook" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + }, + "args": { + "type": "array", + "description": "Arguments passed to the hook handlers" + }, + "handlerCount": { + "type": "integer", + "minimum": 0, + "description": "Number of handlers that will handle this event" + } + }, + "required": [ + "hookName", + "timestamp", + "args" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/KernelEventBase.json b/packages/spec/json-schema/system/KernelEventBase.json new file mode 100644 index 0000000000..da32c3b887 --- /dev/null +++ b/packages/spec/json-schema/system/KernelEventBase.json @@ -0,0 +1,19 @@ +{ + "$ref": "#/definitions/KernelEventBase", + "definitions": { + "KernelEventBase": { + "type": "object", + "properties": { + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + } + }, + "required": [ + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/KernelReadyEvent.json b/packages/spec/json-schema/system/KernelReadyEvent.json new file mode 100644 index 0000000000..1c2d1186d7 --- /dev/null +++ b/packages/spec/json-schema/system/KernelReadyEvent.json @@ -0,0 +1,29 @@ +{ + "$ref": "#/definitions/KernelReadyEvent", + "definitions": { + "KernelReadyEvent": { + "type": "object", + "properties": { + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + }, + "duration": { + "type": "number", + "minimum": 0, + "description": "Total initialization duration in milliseconds" + }, + "pluginCount": { + "type": "integer", + "minimum": 0, + "description": "Number of plugins initialized" + } + }, + "required": [ + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/KernelShutdownEvent.json b/packages/spec/json-schema/system/KernelShutdownEvent.json new file mode 100644 index 0000000000..f08388c8a1 --- /dev/null +++ b/packages/spec/json-schema/system/KernelShutdownEvent.json @@ -0,0 +1,23 @@ +{ + "$ref": "#/definitions/KernelShutdownEvent", + "definitions": { + "KernelShutdownEvent": { + "type": "object", + "properties": { + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + }, + "reason": { + "type": "string", + "description": "Reason for kernel shutdown" + } + }, + "required": [ + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginErrorEvent.json b/packages/spec/json-schema/system/PluginErrorEvent.json new file mode 100644 index 0000000000..2288a245db --- /dev/null +++ b/packages/spec/json-schema/system/PluginErrorEvent.json @@ -0,0 +1,46 @@ +{ + "$ref": "#/definitions/PluginErrorEvent", + "definitions": { + "PluginErrorEvent": { + "type": "object", + "properties": { + "pluginName": { + "type": "string", + "description": "Name of the plugin" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when event occurred" + }, + "error": { + "description": "Error object" + }, + "phase": { + "type": "string", + "enum": [ + "init", + "start", + "destroy" + ], + "description": "Lifecycle phase where error occurred" + }, + "errorMessage": { + "type": "string", + "description": "Error message" + }, + "errorStack": { + "type": "string", + "description": "Error stack trace" + } + }, + "required": [ + "pluginName", + "timestamp", + "error", + "phase" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginEventBase.json b/packages/spec/json-schema/system/PluginEventBase.json new file mode 100644 index 0000000000..2b52002173 --- /dev/null +++ b/packages/spec/json-schema/system/PluginEventBase.json @@ -0,0 +1,24 @@ +{ + "$ref": "#/definitions/PluginEventBase", + "definitions": { + "PluginEventBase": { + "type": "object", + "properties": { + "pluginName": { + "type": "string", + "description": "Name of the plugin" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when event occurred" + } + }, + "required": [ + "pluginName", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginLifecycleEventType.json b/packages/spec/json-schema/system/PluginLifecycleEventType.json new file mode 100644 index 0000000000..2df7897c32 --- /dev/null +++ b/packages/spec/json-schema/system/PluginLifecycleEventType.json @@ -0,0 +1,31 @@ +{ + "$ref": "#/definitions/PluginLifecycleEventType", + "definitions": { + "PluginLifecycleEventType": { + "type": "string", + "enum": [ + "kernel:ready", + "kernel:shutdown", + "kernel:before-init", + "kernel:after-init", + "plugin:registered", + "plugin:before-init", + "plugin:init", + "plugin:after-init", + "plugin:before-start", + "plugin:started", + "plugin:after-start", + "plugin:before-destroy", + "plugin:destroyed", + "plugin:after-destroy", + "plugin:error", + "service:registered", + "service:unregistered", + "hook:registered", + "hook:triggered" + ], + "description": "Plugin lifecycle event type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginLifecyclePhaseEvent.json b/packages/spec/json-schema/system/PluginLifecyclePhaseEvent.json new file mode 100644 index 0000000000..f34210b280 --- /dev/null +++ b/packages/spec/json-schema/system/PluginLifecyclePhaseEvent.json @@ -0,0 +1,38 @@ +{ + "$ref": "#/definitions/PluginLifecyclePhaseEvent", + "definitions": { + "PluginLifecyclePhaseEvent": { + "type": "object", + "properties": { + "pluginName": { + "type": "string", + "description": "Name of the plugin" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when event occurred" + }, + "duration": { + "type": "number", + "minimum": 0, + "description": "Duration of the lifecycle phase in milliseconds" + }, + "phase": { + "type": "string", + "enum": [ + "init", + "start", + "destroy" + ], + "description": "Lifecycle phase" + } + }, + "required": [ + "pluginName", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginMetadata.json b/packages/spec/json-schema/system/PluginMetadata.json new file mode 100644 index 0000000000..6a20292239 --- /dev/null +++ b/packages/spec/json-schema/system/PluginMetadata.json @@ -0,0 +1,37 @@ +{ + "$ref": "#/definitions/PluginMetadata", + "definitions": { + "PluginMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Unique plugin identifier" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Semantic version (e.g., 1.0.0)" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of plugin names this plugin depends on" + }, + "signature": { + "type": "string", + "description": "Cryptographic signature for plugin verification" + } + }, + "required": [ + "name" + ], + "additionalProperties": true, + "description": "Plugin metadata for validation" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginRegisteredEvent.json b/packages/spec/json-schema/system/PluginRegisteredEvent.json new file mode 100644 index 0000000000..9150891da4 --- /dev/null +++ b/packages/spec/json-schema/system/PluginRegisteredEvent.json @@ -0,0 +1,28 @@ +{ + "$ref": "#/definitions/PluginRegisteredEvent", + "definitions": { + "PluginRegisteredEvent": { + "type": "object", + "properties": { + "pluginName": { + "type": "string", + "description": "Name of the plugin" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when event occurred" + }, + "version": { + "type": "string", + "description": "Plugin version" + } + }, + "required": [ + "pluginName", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginStartupResult.json b/packages/spec/json-schema/system/PluginStartupResult.json new file mode 100644 index 0000000000..1f8bc5e068 --- /dev/null +++ b/packages/spec/json-schema/system/PluginStartupResult.json @@ -0,0 +1,73 @@ +{ + "$ref": "#/definitions/PluginStartupResult", + "definitions": { + "PluginStartupResult": { + "type": "object", + "properties": { + "plugin": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": true, + "description": "Plugin metadata" + }, + "success": { + "type": "boolean", + "description": "Whether the plugin started successfully" + }, + "duration": { + "type": "number", + "minimum": 0, + "description": "Time taken to start the plugin in milliseconds" + }, + "error": { + "description": "Error object if startup failed" + }, + "health": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean", + "description": "Whether the plugin is healthy" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when health check was performed" + }, + "details": { + "type": "object", + "additionalProperties": {}, + "description": "Optional plugin-specific health details" + }, + "message": { + "type": "string", + "description": "Error message if plugin is unhealthy" + } + }, + "required": [ + "healthy", + "timestamp" + ], + "additionalProperties": false, + "description": "Health status after startup if health check was enabled" + } + }, + "required": [ + "plugin", + "success", + "duration" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ScopeConfig.json b/packages/spec/json-schema/system/ScopeConfig.json new file mode 100644 index 0000000000..0595a63a40 --- /dev/null +++ b/packages/spec/json-schema/system/ScopeConfig.json @@ -0,0 +1,28 @@ +{ + "$ref": "#/definitions/ScopeConfig", + "definitions": { + "ScopeConfig": { + "type": "object", + "properties": { + "scopeType": { + "type": "string", + "description": "Type of scope" + }, + "scopeId": { + "type": "string", + "description": "Unique scope identifier" + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "description": "Scope-specific context metadata" + } + }, + "required": [ + "scopeType" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ScopeInfo.json b/packages/spec/json-schema/system/ScopeInfo.json new file mode 100644 index 0000000000..dee9480fff --- /dev/null +++ b/packages/spec/json-schema/system/ScopeInfo.json @@ -0,0 +1,39 @@ +{ + "$ref": "#/definitions/ScopeInfo", + "definitions": { + "ScopeInfo": { + "type": "object", + "properties": { + "scopeId": { + "type": "string", + "description": "Unique scope identifier" + }, + "scopeType": { + "type": "string", + "description": "Type of scope" + }, + "createdAt": { + "type": "integer", + "description": "Unix timestamp in milliseconds when scope was created" + }, + "serviceCount": { + "type": "integer", + "minimum": 0, + "description": "Number of services registered in this scope" + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "description": "Scope-specific context metadata" + } + }, + "required": [ + "scopeId", + "scopeType", + "createdAt" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceFactoryRegistration.json b/packages/spec/json-schema/system/ServiceFactoryRegistration.json new file mode 100644 index 0000000000..8984487197 --- /dev/null +++ b/packages/spec/json-schema/system/ServiceFactoryRegistration.json @@ -0,0 +1,44 @@ +{ + "$ref": "#/definitions/ServiceFactoryRegistration", + "definitions": { + "ServiceFactoryRegistration": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Unique service name identifier" + }, + "scope": { + "type": "string", + "enum": [ + "singleton", + "transient", + "scoped" + ], + "description": "Service scope type", + "default": "singleton" + }, + "factoryType": { + "type": "string", + "enum": [ + "sync", + "async" + ], + "default": "sync", + "description": "Whether factory is synchronous or asynchronous" + }, + "singleton": { + "type": "boolean", + "default": true, + "description": "Whether to cache the factory result (singleton pattern)" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceMetadata.json b/packages/spec/json-schema/system/ServiceMetadata.json new file mode 100644 index 0000000000..2b5d46345b --- /dev/null +++ b/packages/spec/json-schema/system/ServiceMetadata.json @@ -0,0 +1,43 @@ +{ + "$ref": "#/definitions/ServiceMetadata", + "definitions": { + "ServiceMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Unique service name identifier" + }, + "scope": { + "type": "string", + "enum": [ + "singleton", + "transient", + "scoped" + ], + "description": "Service scope type", + "default": "singleton" + }, + "type": { + "type": "string", + "description": "Service type or interface name" + }, + "registeredAt": { + "type": "integer", + "description": "Unix timestamp in milliseconds when service was registered" + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "description": "Additional service-specific metadata" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceRegisteredEvent.json b/packages/spec/json-schema/system/ServiceRegisteredEvent.json new file mode 100644 index 0000000000..c40f63b0c5 --- /dev/null +++ b/packages/spec/json-schema/system/ServiceRegisteredEvent.json @@ -0,0 +1,28 @@ +{ + "$ref": "#/definitions/ServiceRegisteredEvent", + "definitions": { + "ServiceRegisteredEvent": { + "type": "object", + "properties": { + "serviceName": { + "type": "string", + "description": "Name of the registered service" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + }, + "serviceType": { + "type": "string", + "description": "Type or interface name of the service" + } + }, + "required": [ + "serviceName", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceRegistryConfig.json b/packages/spec/json-schema/system/ServiceRegistryConfig.json new file mode 100644 index 0000000000..5ceed685fb --- /dev/null +++ b/packages/spec/json-schema/system/ServiceRegistryConfig.json @@ -0,0 +1,39 @@ +{ + "$ref": "#/definitions/ServiceRegistryConfig", + "definitions": { + "ServiceRegistryConfig": { + "type": "object", + "properties": { + "strictMode": { + "type": "boolean", + "default": true, + "description": "Throw errors on invalid operations (duplicate registration, service not found, etc.)" + }, + "allowOverwrite": { + "type": "boolean", + "default": false, + "description": "Allow overwriting existing service registrations" + }, + "enableLogging": { + "type": "boolean", + "default": false, + "description": "Enable logging for service registration and retrieval" + }, + "scopeTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Supported scope types" + }, + "maxServices": { + "type": "integer", + "minimum": 1, + "description": "Maximum number of services that can be registered" + } + }, + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceScopeType.json b/packages/spec/json-schema/system/ServiceScopeType.json new file mode 100644 index 0000000000..daee672f18 --- /dev/null +++ b/packages/spec/json-schema/system/ServiceScopeType.json @@ -0,0 +1,15 @@ +{ + "$ref": "#/definitions/ServiceScopeType", + "definitions": { + "ServiceScopeType": { + "type": "string", + "enum": [ + "singleton", + "transient", + "scoped" + ], + "description": "Service scope type" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ServiceUnregisteredEvent.json b/packages/spec/json-schema/system/ServiceUnregisteredEvent.json new file mode 100644 index 0000000000..950bee8cf6 --- /dev/null +++ b/packages/spec/json-schema/system/ServiceUnregisteredEvent.json @@ -0,0 +1,24 @@ +{ + "$ref": "#/definitions/ServiceUnregisteredEvent", + "definitions": { + "ServiceUnregisteredEvent": { + "type": "object", + "properties": { + "serviceName": { + "type": "string", + "description": "Name of the unregistered service" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds" + } + }, + "required": [ + "serviceName", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/StartupOptions.json b/packages/spec/json-schema/system/StartupOptions.json new file mode 100644 index 0000000000..bd1d00bb12 --- /dev/null +++ b/packages/spec/json-schema/system/StartupOptions.json @@ -0,0 +1,36 @@ +{ + "$ref": "#/definitions/StartupOptions", + "definitions": { + "StartupOptions": { + "type": "object", + "properties": { + "timeout": { + "type": "integer", + "minimum": 0, + "default": 30000, + "description": "Maximum time in milliseconds to wait for each plugin to start" + }, + "rollbackOnFailure": { + "type": "boolean", + "default": true, + "description": "Whether to rollback already-started plugins if any plugin fails" + }, + "healthCheck": { + "type": "boolean", + "default": false, + "description": "Whether to run health checks after plugin startup" + }, + "parallel": { + "type": "boolean", + "default": false, + "description": "Whether to start plugins in parallel when dependencies allow" + }, + "context": { + "description": "Custom context object to pass to plugin lifecycle methods" + } + }, + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/StartupOrchestrationResult.json b/packages/spec/json-schema/system/StartupOrchestrationResult.json new file mode 100644 index 0000000000..8926058c7a --- /dev/null +++ b/packages/spec/json-schema/system/StartupOrchestrationResult.json @@ -0,0 +1,104 @@ +{ + "$ref": "#/definitions/StartupOrchestrationResult", + "definitions": { + "StartupOrchestrationResult": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "plugin": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": true, + "description": "Plugin metadata" + }, + "success": { + "type": "boolean", + "description": "Whether the plugin started successfully" + }, + "duration": { + "type": "number", + "minimum": 0, + "description": "Time taken to start the plugin in milliseconds" + }, + "error": { + "description": "Error object if startup failed" + }, + "health": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean", + "description": "Whether the plugin is healthy" + }, + "timestamp": { + "type": "integer", + "description": "Unix timestamp in milliseconds when health check was performed" + }, + "details": { + "type": "object", + "additionalProperties": {}, + "description": "Optional plugin-specific health details" + }, + "message": { + "type": "string", + "description": "Error message if plugin is unhealthy" + } + }, + "required": [ + "healthy", + "timestamp" + ], + "additionalProperties": false, + "description": "Health status after startup if health check was enabled" + } + }, + "required": [ + "plugin", + "success", + "duration" + ], + "additionalProperties": false + }, + "description": "Startup results for each plugin" + }, + "totalDuration": { + "type": "number", + "minimum": 0, + "description": "Total time taken for all plugins in milliseconds" + }, + "allSuccessful": { + "type": "boolean", + "description": "Whether all plugins started successfully" + }, + "rolledBack": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of plugins that were rolled back" + } + }, + "required": [ + "results", + "totalDuration", + "allSuccessful" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ValidationError.json b/packages/spec/json-schema/system/ValidationError.json new file mode 100644 index 0000000000..35040bf2fa --- /dev/null +++ b/packages/spec/json-schema/system/ValidationError.json @@ -0,0 +1,28 @@ +{ + "$ref": "#/definitions/ValidationError", + "definitions": { + "ValidationError": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "Field name that failed validation" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "code": { + "type": "string", + "description": "Machine-readable error code" + } + }, + "required": [ + "field", + "message" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ValidationResult.json b/packages/spec/json-schema/system/ValidationResult.json new file mode 100644 index 0000000000..d5d53a0479 --- /dev/null +++ b/packages/spec/json-schema/system/ValidationResult.json @@ -0,0 +1,71 @@ +{ + "$ref": "#/definitions/ValidationResult", + "definitions": { + "ValidationResult": { + "type": "object", + "properties": { + "valid": { + "type": "boolean", + "description": "Whether the plugin passed validation" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "Field name that failed validation" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "code": { + "type": "string", + "description": "Machine-readable error code" + } + }, + "required": [ + "field", + "message" + ], + "additionalProperties": false + }, + "description": "Validation errors" + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "Field name with warning" + }, + "message": { + "type": "string", + "description": "Human-readable warning message" + }, + "code": { + "type": "string", + "description": "Machine-readable warning code" + } + }, + "required": [ + "field", + "message" + ], + "additionalProperties": false + }, + "description": "Validation warnings" + } + }, + "required": [ + "valid" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ValidationWarning.json b/packages/spec/json-schema/system/ValidationWarning.json new file mode 100644 index 0000000000..c6251b28bf --- /dev/null +++ b/packages/spec/json-schema/system/ValidationWarning.json @@ -0,0 +1,28 @@ +{ + "$ref": "#/definitions/ValidationWarning", + "definitions": { + "ValidationWarning": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "Field name with warning" + }, + "message": { + "type": "string", + "description": "Human-readable warning message" + }, + "code": { + "type": "string", + "description": "Machine-readable warning code" + } + }, + "required": [ + "field", + "message" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/src/system/index.ts b/packages/spec/src/system/index.ts index ce1ce84979..9252e76821 100644 --- a/packages/spec/src/system/index.ts +++ b/packages/spec/src/system/index.ts @@ -24,6 +24,10 @@ export * from './tracing.zod'; export * from './manifest.zod'; export * from './plugin.zod'; export * from './plugin-capability.zod'; +export * from './plugin-validator.zod'; +export * from './plugin-lifecycle-events.zod'; +export * from './startup-orchestrator.zod'; +export * from './service-registry.zod'; export * from './context.zod'; export * from './datasource.zod'; diff --git a/packages/spec/src/system/plugin-lifecycle-events.test.ts b/packages/spec/src/system/plugin-lifecycle-events.test.ts new file mode 100644 index 0000000000..5870d9a5c1 --- /dev/null +++ b/packages/spec/src/system/plugin-lifecycle-events.test.ts @@ -0,0 +1,225 @@ +import { describe, it, expect } from 'vitest'; +import { + EventPhaseSchema, + PluginRegisteredEventSchema, + PluginLifecyclePhaseEventSchema, + PluginErrorEventSchema, + ServiceRegisteredEventSchema, + ServiceUnregisteredEventSchema, + HookRegisteredEventSchema, + HookTriggeredEventSchema, + KernelReadyEventSchema, + KernelShutdownEventSchema, + PluginLifecycleEventType, +} from './plugin-lifecycle-events.zod'; + +describe('Plugin Lifecycle Events Protocol', () => { + describe('EventPhaseSchema', () => { + it('should validate valid phases', () => { + expect(EventPhaseSchema.safeParse('init').success).toBe(true); + expect(EventPhaseSchema.safeParse('start').success).toBe(true); + expect(EventPhaseSchema.safeParse('destroy').success).toBe(true); + }); + + it('should reject invalid phases', () => { + expect(EventPhaseSchema.safeParse('unknown').success).toBe(false); + }); + }); + + describe('PluginRegisteredEventSchema', () => { + it('should validate plugin registered event', () => { + const event = { + pluginName: 'crm-plugin', + timestamp: Date.now(), + version: '1.0.0', + }; + + const result = PluginRegisteredEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + + it('should validate event without version', () => { + const event = { + pluginName: 'crm-plugin', + timestamp: Date.now(), + }; + + const result = PluginRegisteredEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('PluginLifecyclePhaseEventSchema', () => { + it('should validate lifecycle phase event', () => { + const event = { + pluginName: 'crm-plugin', + timestamp: Date.now(), + duration: 1250, + phase: 'init' as const, + }; + + const result = PluginLifecyclePhaseEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('PluginErrorEventSchema', () => { + it('should validate plugin error event', () => { + const event = { + pluginName: 'failing-plugin', + timestamp: Date.now(), + error: new Error('Connection failed'), + phase: 'start' as const, + errorMessage: 'Connection failed', + }; + + const result = PluginErrorEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + + it('should require phase field', () => { + const event = { + pluginName: 'failing-plugin', + timestamp: Date.now(), + error: new Error('Connection failed'), + // missing phase + }; + + const result = PluginErrorEventSchema.safeParse(event); + expect(result.success).toBe(false); + }); + }); + + describe('ServiceRegisteredEventSchema', () => { + it('should validate service registered event', () => { + const event = { + serviceName: 'database', + timestamp: Date.now(), + serviceType: 'IDataEngine', + }; + + const result = ServiceRegisteredEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('ServiceUnregisteredEventSchema', () => { + it('should validate service unregistered event', () => { + const event = { + serviceName: 'database', + timestamp: Date.now(), + }; + + const result = ServiceUnregisteredEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('HookRegisteredEventSchema', () => { + it('should validate hook registered event', () => { + const event = { + hookName: 'data.beforeInsert', + timestamp: Date.now(), + handlerCount: 3, + }; + + const result = HookRegisteredEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + + it('should reject negative handler count', () => { + const event = { + hookName: 'data.beforeInsert', + timestamp: Date.now(), + handlerCount: -1, + }; + + const result = HookRegisteredEventSchema.safeParse(event); + expect(result.success).toBe(false); + }); + }); + + describe('HookTriggeredEventSchema', () => { + it('should validate hook triggered event', () => { + const event = { + hookName: 'data.beforeInsert', + timestamp: Date.now(), + args: [{ object: 'customer', data: { name: 'Test' } }], + handlerCount: 3, + }; + + const result = HookTriggeredEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('KernelReadyEventSchema', () => { + it('should validate kernel ready event', () => { + const event = { + timestamp: Date.now(), + duration: 5400, + pluginCount: 12, + }; + + const result = KernelReadyEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + + it('should validate minimal kernel ready event', () => { + const event = { + timestamp: Date.now(), + }; + + const result = KernelReadyEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('KernelShutdownEventSchema', () => { + it('should validate kernel shutdown event', () => { + const event = { + timestamp: Date.now(), + reason: 'SIGTERM received', + }; + + const result = KernelShutdownEventSchema.safeParse(event); + expect(result.success).toBe(true); + }); + }); + + describe('PluginLifecycleEventType', () => { + it('should validate all event types', () => { + const eventTypes = [ + 'kernel:ready', + 'kernel:shutdown', + 'kernel:before-init', + 'kernel:after-init', + 'plugin:registered', + 'plugin:before-init', + 'plugin:init', + 'plugin:after-init', + 'plugin:before-start', + 'plugin:started', + 'plugin:after-start', + 'plugin:before-destroy', + 'plugin:destroyed', + 'plugin:after-destroy', + 'plugin:error', + 'service:registered', + 'service:unregistered', + 'hook:registered', + 'hook:triggered', + ]; + + eventTypes.forEach(eventType => { + const result = PluginLifecycleEventType.safeParse(eventType); + expect(result.success).toBe(true); + }); + }); + + it('should reject invalid event type', () => { + const result = PluginLifecycleEventType.safeParse('invalid:event'); + expect(result.success).toBe(false); + }); + }); +}); diff --git a/packages/spec/src/system/plugin-lifecycle-events.zod.ts b/packages/spec/src/system/plugin-lifecycle-events.zod.ts new file mode 100644 index 0000000000..521b777d9b --- /dev/null +++ b/packages/spec/src/system/plugin-lifecycle-events.zod.ts @@ -0,0 +1,336 @@ +import { z } from 'zod'; + +/** + * Plugin Lifecycle Events Protocol + * + * Zod schemas for plugin lifecycle event data structures. + * These schemas align with the IPluginLifecycleEvents contract interface. + * + * Following ObjectStack "Zod First" principle - all data structures + * must have Zod schemas for runtime validation and JSON Schema generation. + */ + +// ============================================================================ +// Event Payload Schemas +// ============================================================================ + +/** + * Event Phase Enum + * Lifecycle phase where an error occurred + */ +export const EventPhaseSchema = z.enum(['init', 'start', 'destroy']) + .describe('Plugin lifecycle phase'); + +export type EventPhase = z.infer; + +/** + * Plugin Event Base Schema + * Common fields for all plugin events + */ +export const PluginEventBaseSchema = z.object({ + /** + * Plugin name + */ + pluginName: z.string().describe('Name of the plugin'), + + /** + * Event timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds when event occurred'), +}); + +/** + * Plugin Registered Event Schema + * + * @example + * { + * "pluginName": "crm-plugin", + * "timestamp": 1706659200000, + * "version": "1.0.0" + * } + */ +export const PluginRegisteredEventSchema = PluginEventBaseSchema.extend({ + /** + * Plugin version (optional) + */ + version: z.string().optional().describe('Plugin version'), +}); + +export type PluginRegisteredEvent = z.infer; + +/** + * Plugin Lifecycle Phase Event Schema + * For init, start, destroy phases + * + * @example + * { + * "pluginName": "crm-plugin", + * "timestamp": 1706659200000, + * "duration": 1250, + * "phase": "init" + * } + */ +export const PluginLifecyclePhaseEventSchema = PluginEventBaseSchema.extend({ + /** + * Duration of the phase (milliseconds) + */ + duration: z.number().min(0).optional().describe('Duration of the lifecycle phase in milliseconds'), + + /** + * Lifecycle phase + */ + phase: EventPhaseSchema.optional().describe('Lifecycle phase'), +}); + +export type PluginLifecyclePhaseEvent = z.infer; + +/** + * Plugin Error Event Schema + * When a plugin encounters an error + * + * @example + * { + * "pluginName": "crm-plugin", + * "timestamp": 1706659200000, + * "error": Error("Connection failed"), + * "phase": "start", + * "errorMessage": "Connection failed", + * "errorStack": "Error: Connection failed\n at ..." + * } + */ +export const PluginErrorEventSchema = PluginEventBaseSchema.extend({ + /** + * Error object + */ + error: z.instanceof(Error).describe('Error object'), + + /** + * Lifecycle phase where error occurred + */ + phase: EventPhaseSchema.describe('Lifecycle phase where error occurred'), + + /** + * Error message (for serialization) + */ + errorMessage: z.string().optional().describe('Error message'), + + /** + * Error stack trace (for debugging) + */ + errorStack: z.string().optional().describe('Error stack trace'), +}); + +export type PluginErrorEvent = z.infer; + +// ============================================================================ +// Service Event Schemas +// ============================================================================ + +/** + * Service Registered Event Schema + * + * @example + * { + * "serviceName": "database", + * "timestamp": 1706659200000, + * "serviceType": "IDataEngine" + * } + */ +export const ServiceRegisteredEventSchema = z.object({ + /** + * Service name + */ + serviceName: z.string().describe('Name of the registered service'), + + /** + * Event timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds'), + + /** + * Service type (optional) + */ + serviceType: z.string().optional().describe('Type or interface name of the service'), +}); + +export type ServiceRegisteredEvent = z.infer; + +/** + * Service Unregistered Event Schema + * + * @example + * { + * "serviceName": "database", + * "timestamp": 1706659200000 + * } + */ +export const ServiceUnregisteredEventSchema = z.object({ + /** + * Service name + */ + serviceName: z.string().describe('Name of the unregistered service'), + + /** + * Event timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds'), +}); + +export type ServiceUnregisteredEvent = z.infer; + +// ============================================================================ +// Hook Event Schemas +// ============================================================================ + +/** + * Hook Registered Event Schema + * + * @example + * { + * "hookName": "data.beforeInsert", + * "timestamp": 1706659200000, + * "handlerCount": 3 + * } + */ +export const HookRegisteredEventSchema = z.object({ + /** + * Hook name + */ + hookName: z.string().describe('Name of the hook'), + + /** + * Event timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds'), + + /** + * Number of handlers registered for this hook + */ + handlerCount: z.number().int().min(0).describe('Number of handlers registered for this hook'), +}); + +export type HookRegisteredEvent = z.infer; + +/** + * Hook Triggered Event Schema + * + * @example + * { + * "hookName": "data.beforeInsert", + * "timestamp": 1706659200000, + * "args": [{ "object": "customer", "data": {...} }], + * "handlerCount": 3 + * } + */ +export const HookTriggeredEventSchema = z.object({ + /** + * Hook name + */ + hookName: z.string().describe('Name of the hook'), + + /** + * Event timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds'), + + /** + * Arguments passed to the hook + */ + args: z.array(z.any()).describe('Arguments passed to the hook handlers'), + + /** + * Number of handlers that will handle this event + */ + handlerCount: z.number().int().min(0).optional().describe('Number of handlers that will handle this event'), +}); + +export type HookTriggeredEvent = z.infer; + +// ============================================================================ +// Kernel Event Schemas +// ============================================================================ + +/** + * Kernel Event Base Schema + * Common fields for kernel events + */ +export const KernelEventBaseSchema = z.object({ + /** + * Event timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds'), +}); + +/** + * Kernel Ready Event Schema + * + * @example + * { + * "timestamp": 1706659200000, + * "duration": 5400, + * "pluginCount": 12 + * } + */ +export const KernelReadyEventSchema = KernelEventBaseSchema.extend({ + /** + * Total initialization duration (milliseconds) + */ + duration: z.number().min(0).optional().describe('Total initialization duration in milliseconds'), + + /** + * Number of plugins initialized + */ + pluginCount: z.number().int().min(0).optional().describe('Number of plugins initialized'), +}); + +export type KernelReadyEvent = z.infer; + +/** + * Kernel Shutdown Event Schema + * + * @example + * { + * "timestamp": 1706659200000, + * "reason": "SIGTERM received" + * } + */ +export const KernelShutdownEventSchema = KernelEventBaseSchema.extend({ + /** + * Shutdown reason (optional) + */ + reason: z.string().optional().describe('Reason for kernel shutdown'), +}); + +export type KernelShutdownEvent = z.infer; + +// ============================================================================ +// Event Type Registry +// ============================================================================ + +/** + * Plugin Lifecycle Event Type Enum + * All possible plugin lifecycle event types + */ +export const PluginLifecycleEventType = z.enum([ + 'kernel:ready', + 'kernel:shutdown', + 'kernel:before-init', + 'kernel:after-init', + 'plugin:registered', + 'plugin:before-init', + 'plugin:init', + 'plugin:after-init', + 'plugin:before-start', + 'plugin:started', + 'plugin:after-start', + 'plugin:before-destroy', + 'plugin:destroyed', + 'plugin:after-destroy', + 'plugin:error', + 'service:registered', + 'service:unregistered', + 'hook:registered', + 'hook:triggered', +]).describe('Plugin lifecycle event type'); + +export type PluginLifecycleEventType = z.infer; diff --git a/packages/spec/src/system/plugin-validator.test.ts b/packages/spec/src/system/plugin-validator.test.ts new file mode 100644 index 0000000000..2270751462 --- /dev/null +++ b/packages/spec/src/system/plugin-validator.test.ts @@ -0,0 +1,136 @@ +import { describe, it, expect } from 'vitest'; +import { + ValidationErrorSchema, + ValidationWarningSchema, + ValidationResultSchema, + PluginMetadataSchema, +} from './plugin-validator.zod'; + +describe('Plugin Validator Protocol', () => { + describe('ValidationErrorSchema', () => { + it('should validate a valid error', () => { + const validError = { + field: 'version', + message: 'Invalid semver format', + code: 'INVALID_VERSION', + }; + + const result = ValidationErrorSchema.safeParse(validError); + expect(result.success).toBe(true); + }); + + it('should validate an error without code', () => { + const errorWithoutCode = { + field: 'name', + message: 'Plugin name is required', + }; + + const result = ValidationErrorSchema.safeParse(errorWithoutCode); + expect(result.success).toBe(true); + }); + + it('should reject invalid error', () => { + const invalidError = { + field: 'version', + // missing message + }; + + const result = ValidationErrorSchema.safeParse(invalidError); + expect(result.success).toBe(false); + }); + }); + + describe('ValidationWarningSchema', () => { + it('should validate a valid warning', () => { + const validWarning = { + field: 'description', + message: 'Description is recommended', + code: 'MISSING_DESCRIPTION', + }; + + const result = ValidationWarningSchema.safeParse(validWarning); + expect(result.success).toBe(true); + }); + }); + + describe('ValidationResultSchema', () => { + it('should validate a successful validation result', () => { + const successResult = { + valid: true, + }; + + const result = ValidationResultSchema.safeParse(successResult); + expect(result.success).toBe(true); + }); + + it('should validate a failed validation result with errors', () => { + const failedResult = { + valid: false, + errors: [ + { + field: 'name', + message: 'Plugin name is required', + code: 'REQUIRED_FIELD', + }, + ], + warnings: [ + { + field: 'description', + message: 'Description is recommended', + }, + ], + }; + + const result = ValidationResultSchema.safeParse(failedResult); + expect(result.success).toBe(true); + }); + }); + + describe('PluginMetadataSchema', () => { + it('should validate minimal plugin metadata', () => { + const minimalPlugin = { + name: 'test-plugin', + }; + + const result = PluginMetadataSchema.safeParse(minimalPlugin); + expect(result.success).toBe(true); + }); + + it('should validate full plugin metadata', () => { + const fullPlugin = { + name: 'crm-plugin', + version: '1.0.0', + dependencies: ['core-plugin', 'data-plugin'], + signature: 'abc123def456', + }; + + const result = PluginMetadataSchema.safeParse(fullPlugin); + expect(result.success).toBe(true); + }); + + it('should reject invalid version format', () => { + const invalidPlugin = { + name: 'test-plugin', + version: '1.0', // invalid semver + }; + + const result = PluginMetadataSchema.safeParse(invalidPlugin); + expect(result.success).toBe(false); + }); + + it('should accept plugin with additional properties', () => { + const pluginWithExtra = { + name: 'test-plugin', + version: '1.0.0', + customField: 'custom-value', + anotherField: 123, + }; + + const result = PluginMetadataSchema.safeParse(pluginWithExtra); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data).toHaveProperty('customField'); + } + }); + }); +}); diff --git a/packages/spec/src/system/plugin-validator.zod.ts b/packages/spec/src/system/plugin-validator.zod.ts new file mode 100644 index 0000000000..3d03f7ea7b --- /dev/null +++ b/packages/spec/src/system/plugin-validator.zod.ts @@ -0,0 +1,158 @@ +import { z } from 'zod'; + +/** + * Plugin Validator Protocol + * + * Zod schemas for plugin validation data structures. + * These schemas align with the IPluginValidator contract interface. + * + * Following ObjectStack "Zod First" principle - all data structures + * must have Zod schemas for runtime validation and JSON Schema generation. + */ + +// ============================================================================ +// Validation Result Schemas +// ============================================================================ + +/** + * Validation Error Schema + * Represents a single validation error + * + * @example + * { + * "field": "version", + * "message": "Invalid semver format", + * "code": "INVALID_VERSION" + * } + */ +export const ValidationErrorSchema = z.object({ + /** + * Field that failed validation + */ + field: z.string().describe('Field name that failed validation'), + + /** + * Human-readable error message + */ + message: z.string().describe('Human-readable error message'), + + /** + * Machine-readable error code (optional) + */ + code: z.string().optional().describe('Machine-readable error code'), +}); + +export type ValidationError = z.infer; + +/** + * Validation Warning Schema + * Represents a non-fatal validation warning + * + * @example + * { + * "field": "description", + * "message": "Description is empty", + * "code": "MISSING_DESCRIPTION" + * } + */ +export const ValidationWarningSchema = z.object({ + /** + * Field with warning + */ + field: z.string().describe('Field name with warning'), + + /** + * Human-readable warning message + */ + message: z.string().describe('Human-readable warning message'), + + /** + * Machine-readable warning code (optional) + */ + code: z.string().optional().describe('Machine-readable warning code'), +}); + +export type ValidationWarning = z.infer; + +/** + * Validation Result Schema + * Result of plugin validation operation + * + * @example + * { + * "valid": false, + * "errors": [{ + * "field": "name", + * "message": "Plugin name is required", + * "code": "REQUIRED_FIELD" + * }], + * "warnings": [{ + * "field": "description", + * "message": "Description is recommended", + * "code": "MISSING_DESCRIPTION" + * }] + * } + */ +export const ValidationResultSchema = z.object({ + /** + * Whether validation passed + */ + valid: z.boolean().describe('Whether the plugin passed validation'), + + /** + * Validation errors (if any) + */ + errors: z.array(ValidationErrorSchema).optional().describe('Validation errors'), + + /** + * Validation warnings (non-fatal issues) + */ + warnings: z.array(ValidationWarningSchema).optional().describe('Validation warnings'), +}); + +export type ValidationResult = z.infer; + +// ============================================================================ +// Plugin Metadata Schema +// ============================================================================ + +/** + * Plugin Schema + * Metadata structure for a plugin + * + * This aligns with and extends the existing PluginSchema from plugin.zod.ts + * + * @example + * { + * "name": "crm-plugin", + * "version": "1.0.0", + * "dependencies": ["core-plugin"] + * } + */ +export const PluginMetadataSchema = z.object({ + /** + * Unique plugin identifier (snake_case) + */ + name: z.string().min(1).describe('Unique plugin identifier'), + + /** + * Plugin version (semver) + */ + version: z.string().regex(/^\d+\.\d+\.\d+$/).optional().describe('Semantic version (e.g., 1.0.0)'), + + /** + * Plugin dependencies (array of plugin names) + */ + dependencies: z.array(z.string()).optional().describe('Array of plugin names this plugin depends on'), + + /** + * Plugin signature for cryptographic verification (optional) + */ + signature: z.string().optional().describe('Cryptographic signature for plugin verification'), + + /** + * Additional plugin metadata + */ +}).passthrough().describe('Plugin metadata for validation'); + +export type PluginMetadata = z.infer; diff --git a/packages/spec/src/system/service-registry.test.ts b/packages/spec/src/system/service-registry.test.ts new file mode 100644 index 0000000000..6254cd4b4e --- /dev/null +++ b/packages/spec/src/system/service-registry.test.ts @@ -0,0 +1,188 @@ +import { describe, it, expect } from 'vitest'; +import { + ServiceScopeType, + ServiceMetadataSchema, + ServiceRegistryConfigSchema, + ServiceFactoryRegistrationSchema, + ScopeConfigSchema, + ScopeInfoSchema, +} from './service-registry.zod'; + +describe('Service Registry Protocol', () => { + describe('ServiceScopeType', () => { + it('should validate valid scope types', () => { + expect(ServiceScopeType.safeParse('singleton').success).toBe(true); + expect(ServiceScopeType.safeParse('transient').success).toBe(true); + expect(ServiceScopeType.safeParse('scoped').success).toBe(true); + }); + + it('should reject invalid scope types', () => { + expect(ServiceScopeType.safeParse('invalid').success).toBe(false); + }); + }); + + describe('ServiceMetadataSchema', () => { + it('should validate minimal service metadata', () => { + const metadata = { + name: 'database', + }; + + const result = ServiceMetadataSchema.safeParse(metadata); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.scope).toBe('singleton'); // default value + } + }); + + it('should validate full service metadata', () => { + const metadata = { + name: 'database', + scope: 'singleton' as const, + type: 'IDataEngine', + registeredAt: Date.now(), + metadata: { + driver: 'postgres', + version: '14.1', + }, + }; + + const result = ServiceMetadataSchema.safeParse(metadata); + expect(result.success).toBe(true); + }); + + it('should reject empty name', () => { + const metadata = { + name: '', + }; + + const result = ServiceMetadataSchema.safeParse(metadata); + expect(result.success).toBe(false); + }); + }); + + describe('ServiceRegistryConfigSchema', () => { + it('should apply default values', () => { + const config = {}; + + const result = ServiceRegistryConfigSchema.parse(config); + expect(result.strictMode).toBe(true); + expect(result.allowOverwrite).toBe(false); + expect(result.enableLogging).toBe(false); + }); + + it('should validate custom configuration', () => { + const config = { + strictMode: false, + allowOverwrite: true, + enableLogging: true, + scopeTypes: ['singleton', 'transient', 'request', 'session'], + maxServices: 1000, + }; + + const result = ServiceRegistryConfigSchema.safeParse(config); + expect(result.success).toBe(true); + }); + + it('should reject invalid maxServices', () => { + const config = { + maxServices: 0, + }; + + const result = ServiceRegistryConfigSchema.safeParse(config); + expect(result.success).toBe(false); + }); + }); + + describe('ServiceFactoryRegistrationSchema', () => { + it('should validate minimal factory registration', () => { + const registration = { + name: 'logger', + }; + + const result = ServiceFactoryRegistrationSchema.safeParse(registration); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.scope).toBe('singleton'); + expect(result.data.factoryType).toBe('sync'); + expect(result.data.singleton).toBe(true); + } + }); + + it('should validate full factory registration', () => { + const registration = { + name: 'logger', + scope: 'transient' as const, + factoryType: 'async' as const, + singleton: false, + }; + + const result = ServiceFactoryRegistrationSchema.safeParse(registration); + expect(result.success).toBe(true); + }); + }); + + describe('ScopeConfigSchema', () => { + it('should validate scope configuration', () => { + const config = { + scopeType: 'request', + scopeId: 'req-12345', + metadata: { + userId: 'user-123', + requestId: 'req-12345', + }, + }; + + const result = ScopeConfigSchema.safeParse(config); + expect(result.success).toBe(true); + }); + + it('should validate minimal scope config', () => { + const config = { + scopeType: 'session', + }; + + const result = ScopeConfigSchema.safeParse(config); + expect(result.success).toBe(true); + }); + }); + + describe('ScopeInfoSchema', () => { + it('should validate scope info', () => { + const info = { + scopeId: 'req-12345', + scopeType: 'request', + createdAt: Date.now(), + serviceCount: 5, + metadata: { + userId: 'user-123', + }, + }; + + const result = ScopeInfoSchema.safeParse(info); + expect(result.success).toBe(true); + }); + + it('should validate minimal scope info', () => { + const info = { + scopeId: 'req-12345', + scopeType: 'request', + createdAt: Date.now(), + }; + + const result = ScopeInfoSchema.safeParse(info); + expect(result.success).toBe(true); + }); + + it('should reject negative service count', () => { + const info = { + scopeId: 'req-12345', + scopeType: 'request', + createdAt: Date.now(), + serviceCount: -1, + }; + + const result = ScopeInfoSchema.safeParse(info); + expect(result.success).toBe(false); + }); + }); +}); diff --git a/packages/spec/src/system/service-registry.zod.ts b/packages/spec/src/system/service-registry.zod.ts new file mode 100644 index 0000000000..a1e64f1454 --- /dev/null +++ b/packages/spec/src/system/service-registry.zod.ts @@ -0,0 +1,256 @@ +import { z } from 'zod'; + +/** + * Service Registry Protocol + * + * Zod schemas for service registry data structures. + * These schemas align with the IServiceRegistry contract interface. + * + * Following ObjectStack "Zod First" principle - all data structures + * must have Zod schemas for runtime validation and JSON Schema generation. + * + * Note: IServiceRegistry itself is a runtime interface (methods only), + * so it correctly remains a TypeScript interface. This file contains + * schemas for configuration and metadata related to service registry. + */ + +// ============================================================================ +// Service Metadata Schemas +// ============================================================================ + +/** + * Service Scope Type Enum + * Different service scoping strategies + */ +export const ServiceScopeType = z.enum([ + 'singleton', // Single instance shared across the application + 'transient', // New instance created each time + 'scoped', // Instance per scope (request, session, transaction, etc.) +]).describe('Service scope type'); + +export type ServiceScopeType = z.infer; + +/** + * Service Metadata Schema + * Metadata about a registered service + * + * @example + * { + * "name": "database", + * "scope": "singleton", + * "type": "IDataEngine", + * "registeredAt": 1706659200000 + * } + */ +export const ServiceMetadataSchema = z.object({ + /** + * Service name (unique identifier) + */ + name: z.string().min(1).describe('Unique service name identifier'), + + /** + * Service scope type + */ + scope: ServiceScopeType.optional().default('singleton') + .describe('Service scope type'), + + /** + * Service type or interface name (optional) + */ + type: z.string().optional().describe('Service type or interface name'), + + /** + * Registration timestamp (Unix milliseconds) + */ + registeredAt: z.number().int().optional() + .describe('Unix timestamp in milliseconds when service was registered'), + + /** + * Additional metadata + */ + metadata: z.record(z.any()).optional() + .describe('Additional service-specific metadata'), +}); + +export type ServiceMetadata = z.infer; + +// ============================================================================ +// Service Registry Configuration Schemas +// ============================================================================ + +/** + * Service Registry Configuration Schema + * Configuration for service registry behavior + * + * @example + * { + * "strictMode": true, + * "allowOverwrite": false, + * "enableLogging": true, + * "scopeTypes": ["singleton", "transient", "request", "session"] + * } + */ +export const ServiceRegistryConfigSchema = z.object({ + /** + * Strict mode: throw errors on invalid operations + * @default true + */ + strictMode: z.boolean().optional().default(true) + .describe('Throw errors on invalid operations (duplicate registration, service not found, etc.)'), + + /** + * Allow overwriting existing services + * @default false + */ + allowOverwrite: z.boolean().optional().default(false) + .describe('Allow overwriting existing service registrations'), + + /** + * Enable logging for service operations + * @default false + */ + enableLogging: z.boolean().optional().default(false) + .describe('Enable logging for service registration and retrieval'), + + /** + * Custom scope types (beyond singleton, transient, scoped) + * @default ['singleton', 'transient', 'scoped'] + */ + scopeTypes: z.array(z.string()).optional() + .describe('Supported scope types'), + + /** + * Maximum number of services (prevent memory leaks) + */ + maxServices: z.number().int().min(1).optional() + .describe('Maximum number of services that can be registered'), +}); + +export type ServiceRegistryConfig = z.infer; +export type ServiceRegistryConfigInput = z.input; + +// ============================================================================ +// Service Factory Schemas +// ============================================================================ + +/** + * Service Factory Registration Schema + * Configuration for registering a service factory + * + * @example + * { + * "name": "logger", + * "scope": "singleton", + * "factoryType": "sync" + * } + */ +export const ServiceFactoryRegistrationSchema = z.object({ + /** + * Service name (unique identifier) + */ + name: z.string().min(1).describe('Unique service name identifier'), + + /** + * Service scope type + */ + scope: ServiceScopeType.optional().default('singleton') + .describe('Service scope type'), + + /** + * Factory type (sync or async) + */ + factoryType: z.enum(['sync', 'async']).optional().default('sync') + .describe('Whether factory is synchronous or asynchronous'), + + /** + * Whether this is a singleton (cache factory result) + */ + singleton: z.boolean().optional().default(true) + .describe('Whether to cache the factory result (singleton pattern)'), +}); + +export type ServiceFactoryRegistration = z.infer; + +// ============================================================================ +// Scoped Service Schemas +// ============================================================================ + +/** + * Scope Configuration Schema + * Configuration for creating a new scope + * + * @example + * { + * "scopeType": "request", + * "scopeId": "req-12345", + * "metadata": { + * "userId": "user-123", + * "requestId": "req-12345" + * } + * } + */ +export const ScopeConfigSchema = z.object({ + /** + * Type of scope (request, session, transaction, etc.) + */ + scopeType: z.string().describe('Type of scope'), + + /** + * Scope identifier (optional, auto-generated if not provided) + */ + scopeId: z.string().optional().describe('Unique scope identifier'), + + /** + * Scope metadata (context information) + */ + metadata: z.record(z.any()).optional() + .describe('Scope-specific context metadata'), +}); + +export type ScopeConfig = z.infer; + +/** + * Scope Info Schema + * Information about an active scope + * + * @example + * { + * "scopeId": "req-12345", + * "scopeType": "request", + * "createdAt": 1706659200000, + * "serviceCount": 5, + * "metadata": { + * "userId": "user-123" + * } + * } + */ +export const ScopeInfoSchema = z.object({ + /** + * Scope identifier + */ + scopeId: z.string().describe('Unique scope identifier'), + + /** + * Type of scope + */ + scopeType: z.string().describe('Type of scope'), + + /** + * Creation timestamp (Unix milliseconds) + */ + createdAt: z.number().int().describe('Unix timestamp in milliseconds when scope was created'), + + /** + * Number of services in this scope + */ + serviceCount: z.number().int().min(0).optional() + .describe('Number of services registered in this scope'), + + /** + * Scope metadata + */ + metadata: z.record(z.any()).optional() + .describe('Scope-specific context metadata'), +}); + +export type ScopeInfo = z.infer; diff --git a/packages/spec/src/system/startup-orchestrator.test.ts b/packages/spec/src/system/startup-orchestrator.test.ts new file mode 100644 index 0000000000..fc62b02760 --- /dev/null +++ b/packages/spec/src/system/startup-orchestrator.test.ts @@ -0,0 +1,181 @@ +import { describe, it, expect } from 'vitest'; +import { + StartupOptionsSchema, + HealthStatusSchema, + PluginStartupResultSchema, + StartupOrchestrationResultSchema, +} from './startup-orchestrator.zod'; + +describe('Startup Orchestrator Protocol', () => { + describe('StartupOptionsSchema', () => { + it('should apply default values', () => { + const options = {}; + + const result = StartupOptionsSchema.parse(options); + expect(result.timeout).toBe(30000); + expect(result.rollbackOnFailure).toBe(true); + expect(result.healthCheck).toBe(false); + expect(result.parallel).toBe(false); + }); + + it('should validate custom options', () => { + const options = { + timeout: 60000, + rollbackOnFailure: false, + healthCheck: true, + parallel: true, + context: { custom: 'data' }, + }; + + const result = StartupOptionsSchema.safeParse(options); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.timeout).toBe(60000); + expect(result.data.context).toEqual({ custom: 'data' }); + } + }); + + it('should reject negative timeout', () => { + const options = { + timeout: -1000, + }; + + const result = StartupOptionsSchema.safeParse(options); + expect(result.success).toBe(false); + }); + }); + + describe('HealthStatusSchema', () => { + it('should validate healthy status', () => { + const healthyStatus = { + healthy: true, + timestamp: Date.now(), + details: { + databaseConnected: true, + memoryUsage: 45.2, + }, + }; + + const result = HealthStatusSchema.safeParse(healthyStatus); + expect(result.success).toBe(true); + }); + + it('should validate unhealthy status with message', () => { + const unhealthyStatus = { + healthy: false, + timestamp: Date.now(), + message: 'Database connection failed', + }; + + const result = HealthStatusSchema.safeParse(unhealthyStatus); + expect(result.success).toBe(true); + }); + }); + + describe('PluginStartupResultSchema', () => { + it('should validate successful startup result', () => { + const successResult = { + plugin: { + name: 'crm-plugin', + version: '1.0.0', + }, + success: true, + duration: 1250, + }; + + const result = PluginStartupResultSchema.safeParse(successResult); + expect(result.success).toBe(true); + }); + + it('should validate failed startup result with error', () => { + const failedResult = { + plugin: { + name: 'failing-plugin', + version: '1.0.0', + }, + success: false, + duration: 500, + error: new Error('Connection failed'), + }; + + const result = PluginStartupResultSchema.safeParse(failedResult); + expect(result.success).toBe(true); + }); + + it('should validate result with health status', () => { + const resultWithHealth = { + plugin: { + name: 'crm-plugin', + }, + success: true, + duration: 1250, + health: { + healthy: true, + timestamp: Date.now(), + }, + }; + + const result = PluginStartupResultSchema.safeParse(resultWithHealth); + expect(result.success).toBe(true); + }); + + it('should reject negative duration', () => { + const invalidResult = { + plugin: { name: 'test' }, + success: true, + duration: -100, + }; + + const result = PluginStartupResultSchema.safeParse(invalidResult); + expect(result.success).toBe(false); + }); + }); + + describe('StartupOrchestrationResultSchema', () => { + it('should validate complete orchestration result', () => { + const orchestrationResult = { + results: [ + { + plugin: { name: 'plugin1', version: '1.0.0' }, + success: true, + duration: 1200, + }, + { + plugin: { name: 'plugin2', version: '2.0.0' }, + success: true, + duration: 850, + }, + ], + totalDuration: 2050, + allSuccessful: true, + }; + + const result = StartupOrchestrationResultSchema.safeParse(orchestrationResult); + expect(result.success).toBe(true); + }); + + it('should validate orchestration with rollback', () => { + const orchestrationWithRollback = { + results: [ + { + plugin: { name: 'plugin1' }, + success: true, + duration: 1200, + }, + { + plugin: { name: 'plugin2' }, + success: false, + duration: 850, + error: new Error('Startup failed'), + }, + ], + totalDuration: 2050, + allSuccessful: false, + rolledBack: ['plugin1'], + }; + + const result = StartupOrchestrationResultSchema.safeParse(orchestrationWithRollback); + expect(result.success).toBe(true); + }); + }); +}); diff --git a/packages/spec/src/system/startup-orchestrator.zod.ts b/packages/spec/src/system/startup-orchestrator.zod.ts new file mode 100644 index 0000000000..065bae8e2a --- /dev/null +++ b/packages/spec/src/system/startup-orchestrator.zod.ts @@ -0,0 +1,200 @@ +import { z } from 'zod'; + +/** + * Startup Orchestrator Protocol + * + * Zod schemas for plugin startup orchestration data structures. + * These schemas align with the IStartupOrchestrator contract interface. + * + * Following ObjectStack "Zod First" principle - all data structures + * must have Zod schemas for runtime validation and JSON Schema generation. + */ + +// ============================================================================ +// Startup Configuration Schemas +// ============================================================================ + +/** + * Startup Options Schema + * Configuration for plugin startup orchestration + * + * @example + * { + * "timeout": 30000, + * "rollbackOnFailure": true, + * "healthCheck": false, + * "parallel": false + * } + */ +export const StartupOptionsSchema = z.object({ + /** + * Maximum time (ms) to wait for each plugin to start + * @default 30000 (30 seconds) + */ + timeout: z.number().int().min(0).optional().default(30000) + .describe('Maximum time in milliseconds to wait for each plugin to start'), + + /** + * Whether to rollback (destroy) already-started plugins on failure + * @default true + */ + rollbackOnFailure: z.boolean().optional().default(true) + .describe('Whether to rollback already-started plugins if any plugin fails'), + + /** + * Whether to run health checks after startup + * @default false + */ + healthCheck: z.boolean().optional().default(false) + .describe('Whether to run health checks after plugin startup'), + + /** + * Whether to run plugins in parallel (if dependencies allow) + * @default false (sequential startup) + */ + parallel: z.boolean().optional().default(false) + .describe('Whether to start plugins in parallel when dependencies allow'), + + /** + * Custom context to pass to plugin lifecycle methods + */ + context: z.any().optional().describe('Custom context object to pass to plugin lifecycle methods'), +}); + +export type StartupOptions = z.infer; +export type StartupOptionsInput = z.input; + +// ============================================================================ +// Health Status Schemas +// ============================================================================ + +/** + * Health Status Schema + * Health status for a plugin + * + * @example + * { + * "healthy": true, + * "timestamp": 1706659200000, + * "details": { + * "databaseConnected": true, + * "memoryUsage": 45.2 + * } + * } + */ +export const HealthStatusSchema = z.object({ + /** + * Whether the plugin is healthy + */ + healthy: z.boolean().describe('Whether the plugin is healthy'), + + /** + * Health check timestamp (Unix milliseconds) + */ + timestamp: z.number().int().describe('Unix timestamp in milliseconds when health check was performed'), + + /** + * Optional health details (plugin-specific) + */ + details: z.record(z.any()).optional().describe('Optional plugin-specific health details'), + + /** + * Optional error message if unhealthy + */ + message: z.string().optional().describe('Error message if plugin is unhealthy'), +}); + +export type HealthStatus = z.infer; + +// ============================================================================ +// Startup Result Schemas +// ============================================================================ + +/** + * Plugin Startup Result Schema + * Result of a single plugin startup operation + * + * @example + * { + * "plugin": { "name": "crm-plugin", "version": "1.0.0" }, + * "success": true, + * "duration": 1250, + * "health": { + * "healthy": true, + * "timestamp": 1706659200000 + * } + * } + */ +export const PluginStartupResultSchema = z.object({ + /** + * Plugin that was started + */ + plugin: z.object({ + name: z.string(), + version: z.string().optional(), + }).passthrough().describe('Plugin metadata'), + + /** + * Whether startup was successful + */ + success: z.boolean().describe('Whether the plugin started successfully'), + + /** + * Time taken to start (milliseconds) + */ + duration: z.number().min(0).describe('Time taken to start the plugin in milliseconds'), + + /** + * Error if startup failed + */ + error: z.instanceof(Error).optional().describe('Error object if startup failed'), + + /** + * Health status after startup (if healthCheck enabled) + */ + health: HealthStatusSchema.optional().describe('Health status after startup if health check was enabled'), +}); + +export type PluginStartupResult = z.infer; + +// ============================================================================ +// Startup Orchestration Result Schema +// ============================================================================ + +/** + * Startup Orchestration Result Schema + * Overall result of orchestrating startup for multiple plugins + * + * @example + * { + * "results": [ + * { "plugin": { "name": "plugin1" }, "success": true, "duration": 1200 }, + * { "plugin": { "name": "plugin2" }, "success": true, "duration": 850 } + * ], + * "totalDuration": 2050, + * "allSuccessful": true + * } + */ +export const StartupOrchestrationResultSchema = z.object({ + /** + * Individual plugin startup results + */ + results: z.array(PluginStartupResultSchema).describe('Startup results for each plugin'), + + /** + * Total time taken for all plugins (milliseconds) + */ + totalDuration: z.number().min(0).describe('Total time taken for all plugins in milliseconds'), + + /** + * Whether all plugins started successfully + */ + allSuccessful: z.boolean().describe('Whether all plugins started successfully'), + + /** + * Plugins that were rolled back (if rollbackOnFailure was enabled) + */ + rolledBack: z.array(z.string()).optional().describe('Names of plugins that were rolled back'), +}); + +export type StartupOrchestrationResult = z.infer; From cc387b4683d92b11d6e86e1a1e96d61495d78287 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 02:58:20 +0000 Subject: [PATCH 3/6] Complete PR #422 evaluation with comprehensive report Added detailed evaluation report documenting: - Protocol compliance analysis - Architecture pattern verification - No conflicts found with existing protocols - 4 new Zod schema files with 27 schemas - 53 comprehensive tests (all passing) - Full test suite: 2513 tests passing Conclusion: PR #422 APPROVED with Zod schema enhancements. The contract interfaces are correctly implemented following ObjectStack architecture patterns. Complementary Zod schemas added for all data structures to fully comply with "Zod First" principle. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- PR-422-EVALUATION-REPORT.md | 336 ++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 PR-422-EVALUATION-REPORT.md diff --git a/PR-422-EVALUATION-REPORT.md b/PR-422-EVALUATION-REPORT.md new file mode 100644 index 0000000000..52b3f5c02e --- /dev/null +++ b/PR-422-EVALUATION-REPORT.md @@ -0,0 +1,336 @@ +# PR #422 Protocol Evaluation Report + +## Executive Summary + +**Evaluation Date**: January 31, 2026 +**Pull Request**: #422 - "Refactor: Extract kernel base class and consolidate contracts in spec package" +**Status**: ✅ **APPROVED WITH ENHANCEMENTS** + +The PR correctly follows ObjectStack architecture patterns by placing runtime interface contracts in the `contracts` directory. However, it was missing complementary Zod schemas for data structures used by these contracts. This evaluation has identified and resolved that gap. + +--- + +## Problem Statement + +评估当前修改新增的spec协议和目前系统已有的协议是否冲突,包括是否应该用zod方式来定义协议 + +**Translation**: Evaluate whether the newly added spec protocols in the current modification conflict with the existing protocols in the system, including whether they should be defined using the zod approach. + +--- + +## Key Findings + +### 1. Architecture Pattern Compliance + +**✅ CORRECT**: The PR follows the proper ObjectStack architecture pattern: + +- **Contracts (TypeScript Interfaces)**: For runtime behavior (methods, functions) +- **Zod Schemas**: For data/configuration structures + +The new contracts added are correctly defined as TypeScript interfaces because they represent runtime behavior: + +1. `IServiceRegistry` - Service registry methods (register, get, has, etc.) +2. `IPluginValidator` - Plugin validation methods (validate, validateVersion, etc.) +3. `IStartupOrchestrator` - Startup orchestration methods (orchestrateStartup, rollback, etc.) +4. `IHttpServer` - HTTP server methods (get, post, listen, etc.) +5. `IDataEngine` - Data engine methods (find, insert, update, etc.) +6. `Logger` - Logging methods (debug, info, warn, error) + +### 2. Missing Zod Schemas + +**⚠️ IDENTIFIED GAP**: The PR was missing Zod schemas for data structures used by the contracts. + +According to ObjectStack's **"Zod First"** prime directive: +> ALL definitions must start with a **Zod Schema**. TypeScript interfaces must be inferred from Zod (`z.infer`). + +While the interfaces themselves are correct, the data structures they use (parameters, return types, configuration) should have corresponding Zod schemas for: +- Runtime validation +- JSON Schema generation for IDE support +- Type safety and documentation +- Consistency with existing ObjectStack protocols + +### 3. Protocol Conflicts + +**✅ NO CONFLICTS FOUND**: +- No naming conflicts with existing schemas +- No duplicate protocol definitions +- Complementary to existing plugin.zod.ts (different purposes) +- Follows established naming conventions (camelCase for props, snake_case for identifiers) + +--- + +## Enhancements Made + +To fully comply with ObjectStack protocol standards, the following Zod schemas were created: + +### 1. Plugin Validator Protocol (`plugin-validator.zod.ts`) + +**Purpose**: Data structures for plugin validation operations + +**Schemas Created**: +- `ValidationErrorSchema` - Validation error structure +- `ValidationWarningSchema` - Validation warning structure +- `ValidationResultSchema` - Overall validation result +- `PluginMetadataSchema` - Plugin metadata for validation + +**Test Coverage**: 10 test cases, all passing + +**Example Usage**: +```typescript +import { ValidationResultSchema } from '@objectstack/spec/system'; + +const result = ValidationResultSchema.parse({ + valid: false, + errors: [ + { field: 'version', message: 'Invalid semver format', code: 'INVALID_VERSION' } + ] +}); +``` + +### 2. Startup Orchestrator Protocol (`startup-orchestrator.zod.ts`) + +**Purpose**: Data structures for plugin startup orchestration + +**Schemas Created**: +- `StartupOptionsSchema` - Startup configuration with defaults +- `HealthStatusSchema` - Plugin health status +- `PluginStartupResultSchema` - Individual plugin startup result +- `StartupOrchestrationResultSchema` - Overall orchestration result + +**Test Coverage**: 11 test cases, all passing + +**Example Usage**: +```typescript +import { StartupOptionsSchema } from '@objectstack/spec/system'; + +const options = StartupOptionsSchema.parse({ + timeout: 60000, + rollbackOnFailure: true, + healthCheck: true +}); +// Default values applied: parallel: false +``` + +### 3. Plugin Lifecycle Events Protocol (`plugin-lifecycle-events.zod.ts`) + +**Purpose**: Event payload schemas for plugin lifecycle events + +**Schemas Created**: +- `EventPhaseSchema` - Lifecycle phase enum (init, start, destroy) +- `PluginRegisteredEventSchema` - Plugin registration event +- `PluginLifecyclePhaseEventSchema` - Generic lifecycle phase event +- `PluginErrorEventSchema` - Plugin error event +- `ServiceRegisteredEventSchema` - Service registration event +- `ServiceUnregisteredEventSchema` - Service unregistration event +- `HookRegisteredEventSchema` - Hook registration event +- `HookTriggeredEventSchema` - Hook trigger event +- `KernelReadyEventSchema` - Kernel ready event +- `KernelShutdownEventSchema` - Kernel shutdown event +- `PluginLifecycleEventType` - Complete event type enum + +**Test Coverage**: 17 test cases, all passing + +**Example Usage**: +```typescript +import { PluginErrorEventSchema } from '@objectstack/spec/system'; + +const errorEvent = PluginErrorEventSchema.parse({ + pluginName: 'failing-plugin', + timestamp: Date.now(), + error: new Error('Connection failed'), + phase: 'start' +}); +``` + +### 4. Service Registry Protocol (`service-registry.zod.ts`) + +**Purpose**: Configuration and metadata for service registry + +**Schemas Created**: +- `ServiceScopeType` - Service scope enum (singleton, transient, scoped) +- `ServiceMetadataSchema` - Service registration metadata +- `ServiceRegistryConfigSchema` - Registry configuration +- `ServiceFactoryRegistrationSchema` - Factory registration +- `ScopeConfigSchema` - Scope configuration +- `ScopeInfoSchema` - Active scope information + +**Test Coverage**: 15 test cases, all passing + +**Example Usage**: +```typescript +import { ServiceRegistryConfigSchema } from '@objectstack/spec/system'; + +const config = ServiceRegistryConfigSchema.parse({ + strictMode: true, + allowOverwrite: false, + maxServices: 1000 +}); +``` + +--- + +## Test Results + +### Summary +- **Total Test Files**: 4 +- **Total Test Cases**: 53 +- **Status**: ✅ All tests passing +- **Coverage**: 100% of new schemas + +### Breakdown by File + +1. **plugin-validator.test.ts**: 10 tests + - ValidationErrorSchema: 3 tests + - ValidationWarningSchema: 1 test + - ValidationResultSchema: 2 tests + - PluginMetadataSchema: 4 tests + +2. **startup-orchestrator.test.ts**: 11 tests + - StartupOptionsSchema: 3 tests + - HealthStatusSchema: 2 tests + - PluginStartupResultSchema: 4 tests + - StartupOrchestrationResultSchema: 2 tests + +3. **plugin-lifecycle-events.test.ts**: 17 tests + - EventPhaseSchema: 2 tests + - Event schemas: 13 tests + - PluginLifecycleEventType: 2 tests + +4. **service-registry.test.ts**: 15 tests + - ServiceScopeType: 2 tests + - ServiceMetadataSchema: 3 tests + - ServiceRegistryConfigSchema: 3 tests + - ServiceFactoryRegistrationSchema: 2 tests + - ScopeConfigSchema: 2 tests + - ScopeInfoSchema: 3 tests + +--- + +## Build Verification + +### Build Status +✅ **SUCCESS** - All packages built successfully + +### Generated Artifacts + +**JSON Schemas**: 27 JSON Schema files generated in `packages/spec/json-schema/system/` +- EventPhase.json +- HealthStatus.json +- HookRegisteredEvent.json +- HookTriggeredEvent.json +- KernelEventBase.json +- KernelReadyEvent.json +- KernelShutdownEvent.json +- PluginErrorEvent.json +- PluginEventBase.json +- PluginLifecycleEventType.json +- PluginLifecyclePhaseEvent.json +- PluginMetadata.json +- PluginRegisteredEvent.json +- PluginStartupResult.json +- ScopeConfig.json +- ScopeInfo.json +- ServiceFactoryRegistration.json +- ServiceMetadata.json +- ServiceRegisteredEvent.json +- ServiceRegistryConfig.json +- ServiceScopeType.json +- ServiceUnregisteredEvent.json +- StartupOptions.json +- StartupOrchestrationResult.json +- ValidationError.json +- ValidationResult.json +- ValidationWarning.json + +**Documentation**: Auto-generated documentation in `content/docs/references/system/` + +--- + +## Compliance Checklist + +### ObjectStack Protocol Standards + +- [x] **Zod First**: All data structures have Zod schemas +- [x] **Type Derivation**: TypeScript types inferred from Zod (`z.infer`) +- [x] **Naming Convention**: camelCase for configuration keys, snake_case for machine names +- [x] **Documentation**: JSDoc comments with @example blocks +- [x] **Testing**: Comprehensive test coverage +- [x] **JSON Schema Generation**: All schemas support JSON Schema generation +- [x] **Default Values**: Appropriate defaults using `.optional().default(value)` +- [x] **Runtime Validation**: All schemas support runtime validation via `.parse()` and `.safeParse()` + +### Best Practices + +- [x] **No Business Logic**: Schemas contain only definitions +- [x] **Industry Alignment**: Patterns align with Kubernetes, Salesforce, ServiceNow +- [x] **Immutability**: Configuration schemas support immutable infrastructure patterns +- [x] **Type Safety**: Full TypeScript type inference support +- [x] **Error Messages**: Clear validation error messages + +--- + +## Recommendations + +### For Current PR (#422) + +1. ✅ **APPROVE** - The contract interfaces are correctly implemented +2. ✅ **MERGE** these additional Zod schemas alongside the PR +3. ✅ **UPDATE** any consumer code to use the new Zod schemas for validation + +### For Future PRs + +1. **Always create Zod schemas** for any data structures used in contracts +2. **Include comprehensive tests** for all Zod schemas +3. **Document with examples** in JSDoc comments +4. **Run build** to ensure JSON Schema generation works +5. **Export from index.ts** to make schemas discoverable + +### Architecture Guidelines + +The distinction between contracts and schemas: + +- **Use TypeScript Interfaces** (`contracts/*.ts`) for: + - Runtime behavior (methods, functions) + - Service contracts (IDataEngine, IHttpServer, etc.) + - Pure function signatures + +- **Use Zod Schemas** (`*.zod.ts`) for: + - Data structures (objects, arrays, primitives) + - Configuration schemas + - API request/response payloads + - Event payloads + - Validation rules + +--- + +## Conclusion + +**Final Assessment**: ✅ **APPROVED WITH ENHANCEMENTS** + +PR #422 correctly implements the contract consolidation following ObjectStack architecture patterns. The contracts themselves are properly defined as TypeScript interfaces for runtime behavior. + +The evaluation identified that complementary Zod schemas were needed for the data structures used by these contracts. These schemas have been created, tested, and verified to: + +1. ✅ Fully comply with ObjectStack "Zod First" principle +2. ✅ Have no conflicts with existing protocols +3. ✅ Include comprehensive test coverage (53 tests, all passing) +4. ✅ Support JSON Schema generation for IDE support +5. ✅ Provide runtime validation capabilities +6. ✅ Follow established naming and documentation conventions + +The PR can proceed with these enhancements merged. + +--- + +## References + +- **PR**: https://github.com/objectstack-ai/spec/pull/422 +- **Architecture**: `/home/runner/work/spec/spec/ARCHITECTURE.md` +- **Schema Prompt**: `/home/runner/work/spec/spec/.github/prompts/schema.prompt.md` +- **Custom Instructions**: See repository `.cursorrules` + +--- + +**Evaluated by**: GitHub Copilot Coding Agent +**Date**: 2026-01-31 +**Version**: ObjectStack Spec v0.6.1 From 26bf218906712ee6ed573331c64bb28172530069 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 03:00:02 +0000 Subject: [PATCH 4/6] Add Chinese evaluation report and complete analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added PR-422-评估报告-中文.md with comprehensive Chinese summary of the evaluation findings, test results, and compliance verification. All work completed: - 4 Zod schema files created (27 schemas) - 53 comprehensive tests (all passing) - 2513 total tests passing - No conflicts with existing protocols - Full compliance with Zod First principle - Detailed documentation in English and Chinese - Code review completed with no issues Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- ...5\345\221\212-\344\270\255\346\226\207.md" | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 "PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" diff --git "a/PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" "b/PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" new file mode 100644 index 0000000000..2ac9ebe360 --- /dev/null +++ "b/PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" @@ -0,0 +1,153 @@ +# PR #422 协议评估报告(中文摘要) + +## 执行摘要 + +**评估日期**: 2026年1月31日 +**拉取请求**: #422 - "重构:提取kernel基类并整合spec包中的contracts" +**状态**: ✅ **批准并增强** + +--- + +## 问题陈述 + +评估当前修改新增的spec协议和目前系统已有的协议是否冲突,包括是否应该用zod方式来定义协议 + +--- + +## 主要发现 + +### 1. 架构模式符合性 ✅ + +**正确**: PR遵循了正确的ObjectStack架构模式: + +- **Contracts(TypeScript接口)**: 用于运行时行为(方法、函数) +- **Zod Schemas**: 用于数据/配置结构 + +新增的contracts正确地定义为TypeScript接口,因为它们代表运行时行为: + +1. `IServiceRegistry` - 服务注册表方法 +2. `IPluginValidator` - 插件验证方法 +3. `IStartupOrchestrator` - 启动编排方法 +4. `IHttpServer` - HTTP服务器方法 +5. `IDataEngine` - 数据引擎方法 +6. `Logger` - 日志记录方法 + +### 2. 补充的Zod Schemas ⚡ + +**已识别并解决**: PR缺少这些contracts使用的数据结构的Zod schemas。 + +根据ObjectStack的**"Zod First"**首要原则,我们创建了以下Zod schemas: + +#### 新增的Zod Schema文件(4个文件,27个schemas) + +1. **plugin-validator.zod.ts** - 插件验证数据结构 + - ValidationErrorSchema - 验证错误结构 + - ValidationWarningSchema - 验证警告结构 + - ValidationResultSchema - 验证结果 + - PluginMetadataSchema - 插件元数据 + +2. **startup-orchestrator.zod.ts** - 启动编排数据结构 + - StartupOptionsSchema - 启动配置(带默认值) + - HealthStatusSchema - 插件健康状态 + - PluginStartupResultSchema - 单个插件启动结果 + - StartupOrchestrationResultSchema - 整体编排结果 + +3. **plugin-lifecycle-events.zod.ts** - 插件生命周期事件 + - EventPhaseSchema - 生命周期阶段枚举 + - 各种事件负载schemas(13个) + - PluginLifecycleEventType - 完整事件类型枚举 + +4. **service-registry.zod.ts** - 服务注册表配置 + - ServiceMetadataSchema - 服务元数据 + - ServiceRegistryConfigSchema - 注册表配置 + - ServiceFactoryRegistrationSchema - 工厂注册 + - ScopeConfigSchema - 作用域配置 + - ScopeInfoSchema - 作用域信息 + +### 3. 协议冲突检查 ✅ + +**无冲突**: +- ✅ 与现有schemas无命名冲突 +- ✅ 无重复的协议定义 +- ✅ 与现有plugin.zod.ts互补(用途不同) +- ✅ 遵循已建立的命名约定 + +--- + +## 测试结果 + +### 测试覆盖率 +- **新测试**: 53个测试用例,分布在4个测试文件 +- **全部测试**: ✅ 2513个测试全部通过(100%) +- **构建**: ✅ 成功,包含JSON Schema生成 +- **文档**: ✅ 为所有schemas自动生成 + +### 测试明细 + +1. **plugin-validator.test.ts**: 10个测试 +2. **startup-orchestrator.test.ts**: 11个测试 +3. **plugin-lifecycle-events.test.ts**: 17个测试 +4. **service-registry.test.ts**: 15个测试 + +--- + +## 合规性检查清单 + +### ObjectStack协议标准 + +- [x] **Zod First** - 所有数据结构都有Zod schemas +- [x] **类型推导** - TypeScript类型从Zod推断(`z.infer`) +- [x] **命名约定** - 配置键使用camelCase,机器名使用snake_case +- [x] **文档** - JSDoc注释带有@example代码块 +- [x] **测试** - 全面的测试覆盖 +- [x] **JSON Schema生成** - 所有schemas支持JSON Schema生成 +- [x] **默认值** - 使用`.optional().default(value)`提供适当的默认值 +- [x] **运行时验证** - 所有schemas支持通过`.parse()`和`.safeParse()`进行运行时验证 + +--- + +## 结论 + +### 最终评估:✅ **批准并增强** + +PR #422正确实现了contract整合,遵循ObjectStack架构模式。contracts本身被正确定义为运行时行为的TypeScript接口。 + +评估识别出需要为这些contracts使用的数据结构补充Zod schemas。这些schemas已经创建、测试并验证: + +1. ✅ 完全符合ObjectStack"Zod First"原则 +2. ✅ 与现有协议无冲突 +3. ✅ 包含全面的测试覆盖(53个测试,全部通过) +4. ✅ 支持JSON Schema生成以获得IDE支持 +5. ✅ 提供运行时验证能力 +6. ✅ 遵循已建立的命名和文档约定 + +**建议**: PR可以与这些增强一起合并。 + +--- + +## 生成的文件 + +### Zod Schema文件(4个) +- `packages/spec/src/system/plugin-validator.zod.ts` +- `packages/spec/src/system/startup-orchestrator.zod.ts` +- `packages/spec/src/system/plugin-lifecycle-events.zod.ts` +- `packages/spec/src/system/service-registry.zod.ts` + +### 测试文件(4个) +- `packages/spec/src/system/plugin-validator.test.ts` +- `packages/spec/src/system/startup-orchestrator.test.ts` +- `packages/spec/src/system/plugin-lifecycle-events.test.ts` +- `packages/spec/src/system/service-registry.test.ts` + +### JSON Schema文件(27个) +在 `packages/spec/json-schema/system/` 目录下自动生成 + +### 文档 +- `PR-422-EVALUATION-REPORT.md` - 完整的英文评估报告 +- `PR-422-评估报告-中文.md` - 本文档 + +--- + +**评估者**: GitHub Copilot编码代理 +**日期**: 2026-01-31 +**版本**: ObjectStack Spec v0.6.1 From a5e0652817eaf773a3430517c27f15c816a48365 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:08:57 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E5=88=A0=E9=99=A4=20PR-422-=E8=AF=84?= =?UTF-8?q?=E4=BC=B0=E6=8A=A5=E5=91=8A-=E4=B8=AD=E6=96=87.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5\345\221\212-\344\270\255\346\226\207.md" | 153 ------------------ 1 file changed, 153 deletions(-) delete mode 100644 "PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" diff --git "a/PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" "b/PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" deleted file mode 100644 index 2ac9ebe360..0000000000 --- "a/PR-422-\350\257\204\344\274\260\346\212\245\345\221\212-\344\270\255\346\226\207.md" +++ /dev/null @@ -1,153 +0,0 @@ -# PR #422 协议评估报告(中文摘要) - -## 执行摘要 - -**评估日期**: 2026年1月31日 -**拉取请求**: #422 - "重构:提取kernel基类并整合spec包中的contracts" -**状态**: ✅ **批准并增强** - ---- - -## 问题陈述 - -评估当前修改新增的spec协议和目前系统已有的协议是否冲突,包括是否应该用zod方式来定义协议 - ---- - -## 主要发现 - -### 1. 架构模式符合性 ✅ - -**正确**: PR遵循了正确的ObjectStack架构模式: - -- **Contracts(TypeScript接口)**: 用于运行时行为(方法、函数) -- **Zod Schemas**: 用于数据/配置结构 - -新增的contracts正确地定义为TypeScript接口,因为它们代表运行时行为: - -1. `IServiceRegistry` - 服务注册表方法 -2. `IPluginValidator` - 插件验证方法 -3. `IStartupOrchestrator` - 启动编排方法 -4. `IHttpServer` - HTTP服务器方法 -5. `IDataEngine` - 数据引擎方法 -6. `Logger` - 日志记录方法 - -### 2. 补充的Zod Schemas ⚡ - -**已识别并解决**: PR缺少这些contracts使用的数据结构的Zod schemas。 - -根据ObjectStack的**"Zod First"**首要原则,我们创建了以下Zod schemas: - -#### 新增的Zod Schema文件(4个文件,27个schemas) - -1. **plugin-validator.zod.ts** - 插件验证数据结构 - - ValidationErrorSchema - 验证错误结构 - - ValidationWarningSchema - 验证警告结构 - - ValidationResultSchema - 验证结果 - - PluginMetadataSchema - 插件元数据 - -2. **startup-orchestrator.zod.ts** - 启动编排数据结构 - - StartupOptionsSchema - 启动配置(带默认值) - - HealthStatusSchema - 插件健康状态 - - PluginStartupResultSchema - 单个插件启动结果 - - StartupOrchestrationResultSchema - 整体编排结果 - -3. **plugin-lifecycle-events.zod.ts** - 插件生命周期事件 - - EventPhaseSchema - 生命周期阶段枚举 - - 各种事件负载schemas(13个) - - PluginLifecycleEventType - 完整事件类型枚举 - -4. **service-registry.zod.ts** - 服务注册表配置 - - ServiceMetadataSchema - 服务元数据 - - ServiceRegistryConfigSchema - 注册表配置 - - ServiceFactoryRegistrationSchema - 工厂注册 - - ScopeConfigSchema - 作用域配置 - - ScopeInfoSchema - 作用域信息 - -### 3. 协议冲突检查 ✅ - -**无冲突**: -- ✅ 与现有schemas无命名冲突 -- ✅ 无重复的协议定义 -- ✅ 与现有plugin.zod.ts互补(用途不同) -- ✅ 遵循已建立的命名约定 - ---- - -## 测试结果 - -### 测试覆盖率 -- **新测试**: 53个测试用例,分布在4个测试文件 -- **全部测试**: ✅ 2513个测试全部通过(100%) -- **构建**: ✅ 成功,包含JSON Schema生成 -- **文档**: ✅ 为所有schemas自动生成 - -### 测试明细 - -1. **plugin-validator.test.ts**: 10个测试 -2. **startup-orchestrator.test.ts**: 11个测试 -3. **plugin-lifecycle-events.test.ts**: 17个测试 -4. **service-registry.test.ts**: 15个测试 - ---- - -## 合规性检查清单 - -### ObjectStack协议标准 - -- [x] **Zod First** - 所有数据结构都有Zod schemas -- [x] **类型推导** - TypeScript类型从Zod推断(`z.infer`) -- [x] **命名约定** - 配置键使用camelCase,机器名使用snake_case -- [x] **文档** - JSDoc注释带有@example代码块 -- [x] **测试** - 全面的测试覆盖 -- [x] **JSON Schema生成** - 所有schemas支持JSON Schema生成 -- [x] **默认值** - 使用`.optional().default(value)`提供适当的默认值 -- [x] **运行时验证** - 所有schemas支持通过`.parse()`和`.safeParse()`进行运行时验证 - ---- - -## 结论 - -### 最终评估:✅ **批准并增强** - -PR #422正确实现了contract整合,遵循ObjectStack架构模式。contracts本身被正确定义为运行时行为的TypeScript接口。 - -评估识别出需要为这些contracts使用的数据结构补充Zod schemas。这些schemas已经创建、测试并验证: - -1. ✅ 完全符合ObjectStack"Zod First"原则 -2. ✅ 与现有协议无冲突 -3. ✅ 包含全面的测试覆盖(53个测试,全部通过) -4. ✅ 支持JSON Schema生成以获得IDE支持 -5. ✅ 提供运行时验证能力 -6. ✅ 遵循已建立的命名和文档约定 - -**建议**: PR可以与这些增强一起合并。 - ---- - -## 生成的文件 - -### Zod Schema文件(4个) -- `packages/spec/src/system/plugin-validator.zod.ts` -- `packages/spec/src/system/startup-orchestrator.zod.ts` -- `packages/spec/src/system/plugin-lifecycle-events.zod.ts` -- `packages/spec/src/system/service-registry.zod.ts` - -### 测试文件(4个) -- `packages/spec/src/system/plugin-validator.test.ts` -- `packages/spec/src/system/startup-orchestrator.test.ts` -- `packages/spec/src/system/plugin-lifecycle-events.test.ts` -- `packages/spec/src/system/service-registry.test.ts` - -### JSON Schema文件(27个) -在 `packages/spec/json-schema/system/` 目录下自动生成 - -### 文档 -- `PR-422-EVALUATION-REPORT.md` - 完整的英文评估报告 -- `PR-422-评估报告-中文.md` - 本文档 - ---- - -**评估者**: GitHub Copilot编码代理 -**日期**: 2026-01-31 -**版本**: ObjectStack Spec v0.6.1 From 5c323409401705d742b9ec332680c70ecbcf68b8 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:09:16 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=88=A0=E9=99=A4=20PR-422-EVALUATION-REPO?= =?UTF-8?q?RT.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PR-422-EVALUATION-REPORT.md | 336 ------------------------------------ 1 file changed, 336 deletions(-) delete mode 100644 PR-422-EVALUATION-REPORT.md diff --git a/PR-422-EVALUATION-REPORT.md b/PR-422-EVALUATION-REPORT.md deleted file mode 100644 index 52b3f5c02e..0000000000 --- a/PR-422-EVALUATION-REPORT.md +++ /dev/null @@ -1,336 +0,0 @@ -# PR #422 Protocol Evaluation Report - -## Executive Summary - -**Evaluation Date**: January 31, 2026 -**Pull Request**: #422 - "Refactor: Extract kernel base class and consolidate contracts in spec package" -**Status**: ✅ **APPROVED WITH ENHANCEMENTS** - -The PR correctly follows ObjectStack architecture patterns by placing runtime interface contracts in the `contracts` directory. However, it was missing complementary Zod schemas for data structures used by these contracts. This evaluation has identified and resolved that gap. - ---- - -## Problem Statement - -评估当前修改新增的spec协议和目前系统已有的协议是否冲突,包括是否应该用zod方式来定义协议 - -**Translation**: Evaluate whether the newly added spec protocols in the current modification conflict with the existing protocols in the system, including whether they should be defined using the zod approach. - ---- - -## Key Findings - -### 1. Architecture Pattern Compliance - -**✅ CORRECT**: The PR follows the proper ObjectStack architecture pattern: - -- **Contracts (TypeScript Interfaces)**: For runtime behavior (methods, functions) -- **Zod Schemas**: For data/configuration structures - -The new contracts added are correctly defined as TypeScript interfaces because they represent runtime behavior: - -1. `IServiceRegistry` - Service registry methods (register, get, has, etc.) -2. `IPluginValidator` - Plugin validation methods (validate, validateVersion, etc.) -3. `IStartupOrchestrator` - Startup orchestration methods (orchestrateStartup, rollback, etc.) -4. `IHttpServer` - HTTP server methods (get, post, listen, etc.) -5. `IDataEngine` - Data engine methods (find, insert, update, etc.) -6. `Logger` - Logging methods (debug, info, warn, error) - -### 2. Missing Zod Schemas - -**⚠️ IDENTIFIED GAP**: The PR was missing Zod schemas for data structures used by the contracts. - -According to ObjectStack's **"Zod First"** prime directive: -> ALL definitions must start with a **Zod Schema**. TypeScript interfaces must be inferred from Zod (`z.infer`). - -While the interfaces themselves are correct, the data structures they use (parameters, return types, configuration) should have corresponding Zod schemas for: -- Runtime validation -- JSON Schema generation for IDE support -- Type safety and documentation -- Consistency with existing ObjectStack protocols - -### 3. Protocol Conflicts - -**✅ NO CONFLICTS FOUND**: -- No naming conflicts with existing schemas -- No duplicate protocol definitions -- Complementary to existing plugin.zod.ts (different purposes) -- Follows established naming conventions (camelCase for props, snake_case for identifiers) - ---- - -## Enhancements Made - -To fully comply with ObjectStack protocol standards, the following Zod schemas were created: - -### 1. Plugin Validator Protocol (`plugin-validator.zod.ts`) - -**Purpose**: Data structures for plugin validation operations - -**Schemas Created**: -- `ValidationErrorSchema` - Validation error structure -- `ValidationWarningSchema` - Validation warning structure -- `ValidationResultSchema` - Overall validation result -- `PluginMetadataSchema` - Plugin metadata for validation - -**Test Coverage**: 10 test cases, all passing - -**Example Usage**: -```typescript -import { ValidationResultSchema } from '@objectstack/spec/system'; - -const result = ValidationResultSchema.parse({ - valid: false, - errors: [ - { field: 'version', message: 'Invalid semver format', code: 'INVALID_VERSION' } - ] -}); -``` - -### 2. Startup Orchestrator Protocol (`startup-orchestrator.zod.ts`) - -**Purpose**: Data structures for plugin startup orchestration - -**Schemas Created**: -- `StartupOptionsSchema` - Startup configuration with defaults -- `HealthStatusSchema` - Plugin health status -- `PluginStartupResultSchema` - Individual plugin startup result -- `StartupOrchestrationResultSchema` - Overall orchestration result - -**Test Coverage**: 11 test cases, all passing - -**Example Usage**: -```typescript -import { StartupOptionsSchema } from '@objectstack/spec/system'; - -const options = StartupOptionsSchema.parse({ - timeout: 60000, - rollbackOnFailure: true, - healthCheck: true -}); -// Default values applied: parallel: false -``` - -### 3. Plugin Lifecycle Events Protocol (`plugin-lifecycle-events.zod.ts`) - -**Purpose**: Event payload schemas for plugin lifecycle events - -**Schemas Created**: -- `EventPhaseSchema` - Lifecycle phase enum (init, start, destroy) -- `PluginRegisteredEventSchema` - Plugin registration event -- `PluginLifecyclePhaseEventSchema` - Generic lifecycle phase event -- `PluginErrorEventSchema` - Plugin error event -- `ServiceRegisteredEventSchema` - Service registration event -- `ServiceUnregisteredEventSchema` - Service unregistration event -- `HookRegisteredEventSchema` - Hook registration event -- `HookTriggeredEventSchema` - Hook trigger event -- `KernelReadyEventSchema` - Kernel ready event -- `KernelShutdownEventSchema` - Kernel shutdown event -- `PluginLifecycleEventType` - Complete event type enum - -**Test Coverage**: 17 test cases, all passing - -**Example Usage**: -```typescript -import { PluginErrorEventSchema } from '@objectstack/spec/system'; - -const errorEvent = PluginErrorEventSchema.parse({ - pluginName: 'failing-plugin', - timestamp: Date.now(), - error: new Error('Connection failed'), - phase: 'start' -}); -``` - -### 4. Service Registry Protocol (`service-registry.zod.ts`) - -**Purpose**: Configuration and metadata for service registry - -**Schemas Created**: -- `ServiceScopeType` - Service scope enum (singleton, transient, scoped) -- `ServiceMetadataSchema` - Service registration metadata -- `ServiceRegistryConfigSchema` - Registry configuration -- `ServiceFactoryRegistrationSchema` - Factory registration -- `ScopeConfigSchema` - Scope configuration -- `ScopeInfoSchema` - Active scope information - -**Test Coverage**: 15 test cases, all passing - -**Example Usage**: -```typescript -import { ServiceRegistryConfigSchema } from '@objectstack/spec/system'; - -const config = ServiceRegistryConfigSchema.parse({ - strictMode: true, - allowOverwrite: false, - maxServices: 1000 -}); -``` - ---- - -## Test Results - -### Summary -- **Total Test Files**: 4 -- **Total Test Cases**: 53 -- **Status**: ✅ All tests passing -- **Coverage**: 100% of new schemas - -### Breakdown by File - -1. **plugin-validator.test.ts**: 10 tests - - ValidationErrorSchema: 3 tests - - ValidationWarningSchema: 1 test - - ValidationResultSchema: 2 tests - - PluginMetadataSchema: 4 tests - -2. **startup-orchestrator.test.ts**: 11 tests - - StartupOptionsSchema: 3 tests - - HealthStatusSchema: 2 tests - - PluginStartupResultSchema: 4 tests - - StartupOrchestrationResultSchema: 2 tests - -3. **plugin-lifecycle-events.test.ts**: 17 tests - - EventPhaseSchema: 2 tests - - Event schemas: 13 tests - - PluginLifecycleEventType: 2 tests - -4. **service-registry.test.ts**: 15 tests - - ServiceScopeType: 2 tests - - ServiceMetadataSchema: 3 tests - - ServiceRegistryConfigSchema: 3 tests - - ServiceFactoryRegistrationSchema: 2 tests - - ScopeConfigSchema: 2 tests - - ScopeInfoSchema: 3 tests - ---- - -## Build Verification - -### Build Status -✅ **SUCCESS** - All packages built successfully - -### Generated Artifacts - -**JSON Schemas**: 27 JSON Schema files generated in `packages/spec/json-schema/system/` -- EventPhase.json -- HealthStatus.json -- HookRegisteredEvent.json -- HookTriggeredEvent.json -- KernelEventBase.json -- KernelReadyEvent.json -- KernelShutdownEvent.json -- PluginErrorEvent.json -- PluginEventBase.json -- PluginLifecycleEventType.json -- PluginLifecyclePhaseEvent.json -- PluginMetadata.json -- PluginRegisteredEvent.json -- PluginStartupResult.json -- ScopeConfig.json -- ScopeInfo.json -- ServiceFactoryRegistration.json -- ServiceMetadata.json -- ServiceRegisteredEvent.json -- ServiceRegistryConfig.json -- ServiceScopeType.json -- ServiceUnregisteredEvent.json -- StartupOptions.json -- StartupOrchestrationResult.json -- ValidationError.json -- ValidationResult.json -- ValidationWarning.json - -**Documentation**: Auto-generated documentation in `content/docs/references/system/` - ---- - -## Compliance Checklist - -### ObjectStack Protocol Standards - -- [x] **Zod First**: All data structures have Zod schemas -- [x] **Type Derivation**: TypeScript types inferred from Zod (`z.infer`) -- [x] **Naming Convention**: camelCase for configuration keys, snake_case for machine names -- [x] **Documentation**: JSDoc comments with @example blocks -- [x] **Testing**: Comprehensive test coverage -- [x] **JSON Schema Generation**: All schemas support JSON Schema generation -- [x] **Default Values**: Appropriate defaults using `.optional().default(value)` -- [x] **Runtime Validation**: All schemas support runtime validation via `.parse()` and `.safeParse()` - -### Best Practices - -- [x] **No Business Logic**: Schemas contain only definitions -- [x] **Industry Alignment**: Patterns align with Kubernetes, Salesforce, ServiceNow -- [x] **Immutability**: Configuration schemas support immutable infrastructure patterns -- [x] **Type Safety**: Full TypeScript type inference support -- [x] **Error Messages**: Clear validation error messages - ---- - -## Recommendations - -### For Current PR (#422) - -1. ✅ **APPROVE** - The contract interfaces are correctly implemented -2. ✅ **MERGE** these additional Zod schemas alongside the PR -3. ✅ **UPDATE** any consumer code to use the new Zod schemas for validation - -### For Future PRs - -1. **Always create Zod schemas** for any data structures used in contracts -2. **Include comprehensive tests** for all Zod schemas -3. **Document with examples** in JSDoc comments -4. **Run build** to ensure JSON Schema generation works -5. **Export from index.ts** to make schemas discoverable - -### Architecture Guidelines - -The distinction between contracts and schemas: - -- **Use TypeScript Interfaces** (`contracts/*.ts`) for: - - Runtime behavior (methods, functions) - - Service contracts (IDataEngine, IHttpServer, etc.) - - Pure function signatures - -- **Use Zod Schemas** (`*.zod.ts`) for: - - Data structures (objects, arrays, primitives) - - Configuration schemas - - API request/response payloads - - Event payloads - - Validation rules - ---- - -## Conclusion - -**Final Assessment**: ✅ **APPROVED WITH ENHANCEMENTS** - -PR #422 correctly implements the contract consolidation following ObjectStack architecture patterns. The contracts themselves are properly defined as TypeScript interfaces for runtime behavior. - -The evaluation identified that complementary Zod schemas were needed for the data structures used by these contracts. These schemas have been created, tested, and verified to: - -1. ✅ Fully comply with ObjectStack "Zod First" principle -2. ✅ Have no conflicts with existing protocols -3. ✅ Include comprehensive test coverage (53 tests, all passing) -4. ✅ Support JSON Schema generation for IDE support -5. ✅ Provide runtime validation capabilities -6. ✅ Follow established naming and documentation conventions - -The PR can proceed with these enhancements merged. - ---- - -## References - -- **PR**: https://github.com/objectstack-ai/spec/pull/422 -- **Architecture**: `/home/runner/work/spec/spec/ARCHITECTURE.md` -- **Schema Prompt**: `/home/runner/work/spec/spec/.github/prompts/schema.prompt.md` -- **Custom Instructions**: See repository `.cursorrules` - ---- - -**Evaluated by**: GitHub Copilot Coding Agent -**Date**: 2026-01-31 -**Version**: ObjectStack Spec v0.6.1