From 0bc965b7539ce3314b389ffe9b7e8fa8d36a7f27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:23:20 +0000 Subject: [PATCH 1/3] Initial plan From 5d837af21e6e74bb8a304049151b51d012b73021 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:29:18 +0000 Subject: [PATCH 2/3] Add distributed state, WASM sandbox, registry federation, and AI low-code features Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/ai/index.mdx | 1 + content/docs/references/ai/meta.json | 3 +- .../spec/json-schema/ai/AIOpsAgentConfig.json | 7 + .../ai/AnomalyDetectionConfig.json | 7 + .../json-schema/ai/AutoScalingPolicy.json | 7 + .../ai/PerformanceOptimization.json | 7 + .../ai/RootCauseAnalysisRequest.json | 7 + .../ai/RootCauseAnalysisResult.json | 7 + .../json-schema/ai/SelfHealingAction.json | 7 + .../json-schema/ai/SelfHealingConfig.json | 7 + .../spec/json-schema/hub/RegistryConfig.json | 7 + .../json-schema/hub/RegistrySyncPolicy.json | 7 + .../json-schema/hub/RegistryUpstream.json | 7 + .../system/DistributedStateConfig.json | 7 + .../json-schema/system/RuntimeConfig.json | 7 + packages/spec/src/ai/index.ts | 3 + .../spec/src/ai/plugin-development.zod.ts | 71 +- packages/spec/src/ai/runtime-ops.zod.ts | 679 ++++++++++++++++++ .../spec/src/hub/marketplace-enhanced.zod.ts | 171 +++++ .../system/plugin-lifecycle-advanced.zod.ts | 63 +- .../system/plugin-security-advanced.zod.ts | 131 ++++ 21 files changed, 1205 insertions(+), 8 deletions(-) create mode 100644 packages/spec/json-schema/ai/AIOpsAgentConfig.json create mode 100644 packages/spec/json-schema/ai/AnomalyDetectionConfig.json create mode 100644 packages/spec/json-schema/ai/AutoScalingPolicy.json create mode 100644 packages/spec/json-schema/ai/PerformanceOptimization.json create mode 100644 packages/spec/json-schema/ai/RootCauseAnalysisRequest.json create mode 100644 packages/spec/json-schema/ai/RootCauseAnalysisResult.json create mode 100644 packages/spec/json-schema/ai/SelfHealingAction.json create mode 100644 packages/spec/json-schema/ai/SelfHealingConfig.json create mode 100644 packages/spec/json-schema/hub/RegistryConfig.json create mode 100644 packages/spec/json-schema/hub/RegistrySyncPolicy.json create mode 100644 packages/spec/json-schema/hub/RegistryUpstream.json create mode 100644 packages/spec/json-schema/system/DistributedStateConfig.json create mode 100644 packages/spec/json-schema/system/RuntimeConfig.json create mode 100644 packages/spec/src/ai/runtime-ops.zod.ts diff --git a/content/docs/references/ai/index.mdx b/content/docs/references/ai/index.mdx index 4aaa0c929..37920a4b7 100644 --- a/content/docs/references/ai/index.mdx +++ b/content/docs/references/ai/index.mdx @@ -20,5 +20,6 @@ This section contains all protocol schemas for the ai layer of ObjectStack. + diff --git a/content/docs/references/ai/meta.json b/content/docs/references/ai/meta.json index 41830cd58..cd6ee502e 100644 --- a/content/docs/references/ai/meta.json +++ b/content/docs/references/ai/meta.json @@ -12,6 +12,7 @@ "orchestration", "plugin-development", "predictive", - "rag-pipeline" + "rag-pipeline", + "runtime-ops" ] } \ No newline at end of file diff --git a/packages/spec/json-schema/ai/AIOpsAgentConfig.json b/packages/spec/json-schema/ai/AIOpsAgentConfig.json new file mode 100644 index 000000000..6cf8012e2 --- /dev/null +++ b/packages/spec/json-schema/ai/AIOpsAgentConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/AIOpsAgentConfig", + "definitions": { + "AIOpsAgentConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/AnomalyDetectionConfig.json b/packages/spec/json-schema/ai/AnomalyDetectionConfig.json new file mode 100644 index 000000000..76cc97425 --- /dev/null +++ b/packages/spec/json-schema/ai/AnomalyDetectionConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/AnomalyDetectionConfig", + "definitions": { + "AnomalyDetectionConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/AutoScalingPolicy.json b/packages/spec/json-schema/ai/AutoScalingPolicy.json new file mode 100644 index 000000000..cb1556141 --- /dev/null +++ b/packages/spec/json-schema/ai/AutoScalingPolicy.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/AutoScalingPolicy", + "definitions": { + "AutoScalingPolicy": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/PerformanceOptimization.json b/packages/spec/json-schema/ai/PerformanceOptimization.json new file mode 100644 index 000000000..f15310ad3 --- /dev/null +++ b/packages/spec/json-schema/ai/PerformanceOptimization.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PerformanceOptimization", + "definitions": { + "PerformanceOptimization": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/RootCauseAnalysisRequest.json b/packages/spec/json-schema/ai/RootCauseAnalysisRequest.json new file mode 100644 index 000000000..0df85a99a --- /dev/null +++ b/packages/spec/json-schema/ai/RootCauseAnalysisRequest.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/RootCauseAnalysisRequest", + "definitions": { + "RootCauseAnalysisRequest": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/RootCauseAnalysisResult.json b/packages/spec/json-schema/ai/RootCauseAnalysisResult.json new file mode 100644 index 000000000..4f2fda9c3 --- /dev/null +++ b/packages/spec/json-schema/ai/RootCauseAnalysisResult.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/RootCauseAnalysisResult", + "definitions": { + "RootCauseAnalysisResult": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/SelfHealingAction.json b/packages/spec/json-schema/ai/SelfHealingAction.json new file mode 100644 index 000000000..2322213b1 --- /dev/null +++ b/packages/spec/json-schema/ai/SelfHealingAction.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SelfHealingAction", + "definitions": { + "SelfHealingAction": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/SelfHealingConfig.json b/packages/spec/json-schema/ai/SelfHealingConfig.json new file mode 100644 index 000000000..bb77bef75 --- /dev/null +++ b/packages/spec/json-schema/ai/SelfHealingConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SelfHealingConfig", + "definitions": { + "SelfHealingConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/RegistryConfig.json b/packages/spec/json-schema/hub/RegistryConfig.json new file mode 100644 index 000000000..66d678ce4 --- /dev/null +++ b/packages/spec/json-schema/hub/RegistryConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/RegistryConfig", + "definitions": { + "RegistryConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/RegistrySyncPolicy.json b/packages/spec/json-schema/hub/RegistrySyncPolicy.json new file mode 100644 index 000000000..e3500e1db --- /dev/null +++ b/packages/spec/json-schema/hub/RegistrySyncPolicy.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/RegistrySyncPolicy", + "definitions": { + "RegistrySyncPolicy": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/RegistryUpstream.json b/packages/spec/json-schema/hub/RegistryUpstream.json new file mode 100644 index 000000000..53c1725bd --- /dev/null +++ b/packages/spec/json-schema/hub/RegistryUpstream.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/RegistryUpstream", + "definitions": { + "RegistryUpstream": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/DistributedStateConfig.json b/packages/spec/json-schema/system/DistributedStateConfig.json new file mode 100644 index 000000000..1419e4cb4 --- /dev/null +++ b/packages/spec/json-schema/system/DistributedStateConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/DistributedStateConfig", + "definitions": { + "DistributedStateConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/RuntimeConfig.json b/packages/spec/json-schema/system/RuntimeConfig.json new file mode 100644 index 000000000..99de6df88 --- /dev/null +++ b/packages/spec/json-schema/system/RuntimeConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/RuntimeConfig", + "definitions": { + "RuntimeConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/src/ai/index.ts b/packages/spec/src/ai/index.ts index 17f0ddb5e..8350c0044 100644 --- a/packages/spec/src/ai/index.ts +++ b/packages/spec/src/ai/index.ts @@ -11,12 +11,15 @@ * - Predictive Analytics * - Conversation Memory & Token Management * - Cost Tracking & Budget Management + * - Plugin Development (AI-assisted) + * - Runtime Operations (AIOps) */ export * from './agent.zod'; export * from './agent-action.zod'; export * from './devops-agent.zod'; export * from './plugin-development.zod'; +export * from './runtime-ops.zod'; export * from './model-registry.zod'; export * from './rag-pipeline.zod'; export * from './nlq.zod'; diff --git a/packages/spec/src/ai/plugin-development.zod.ts b/packages/spec/src/ai/plugin-development.zod.ts index 029497bd3..6e4c5d9e1 100644 --- a/packages/spec/src/ai/plugin-development.zod.ts +++ b/packages/spec/src/ai/plugin-development.zod.ts @@ -36,7 +36,17 @@ export const CodeGenerationRequestSchema = z.object({ ]), /** - * Target programming language + * Output format for generated code + */ + outputFormat: z.enum([ + 'source-code', // Generate TypeScript/JavaScript source code + 'low-code-schema', // Generate ObjectStack JSON/YAML schema definitions + 'dsl', // Generate domain-specific language definitions + ]).default('source-code') + .describe('Format of the generated output'), + + /** + * Target programming language (for source-code format) */ language: z.enum(['typescript', 'javascript', 'python']).default('typescript'), @@ -69,7 +79,7 @@ export const CodeGenerationRequestSchema = z.object({ })).optional(), /** - * Code style preferences + * Code style preferences (for source-code format) */ style: z.object({ indentation: z.enum(['tab', '2spaces', '4spaces']).default('2spaces'), @@ -78,6 +88,39 @@ export const CodeGenerationRequestSchema = z.object({ trailingComma: z.boolean().default(true), }).optional(), + /** + * Low-code schema preferences (for low-code-schema format) + */ + schemaOptions: z.object({ + /** + * Schema format + */ + format: z.enum(['json', 'yaml', 'typescript']).default('typescript') + .describe('Output schema format'), + + /** + * Include example data + */ + includeExamples: z.boolean().default(true), + + /** + * Validation strictness + */ + strictValidation: z.boolean().default(true), + + /** + * Generate UI definitions + */ + generateUI: z.boolean().default(true) + .describe('Generate view, dashboard, and page definitions'), + + /** + * Generate data models + */ + generateDataModels: z.boolean().default(true) + .describe('Generate object and field definitions'), + }).optional(), + /** * Additional context */ @@ -135,14 +178,30 @@ export const CodeGenerationRequestSchema = z.object({ */ export const GeneratedCodeSchema = z.object({ /** - * Main plugin code + * Output format used */ - code: z.string(), + outputFormat: z.enum(['source-code', 'low-code-schema', 'dsl']), /** - * Language used + * Main plugin code (for source-code format) */ - language: z.string(), + code: z.string().optional(), + + /** + * Language used (for source-code format) + */ + language: z.string().optional(), + + /** + * Low-code schema definitions (for low-code-schema format) + */ + schemas: z.array(z.object({ + type: z.enum(['object', 'view', 'dashboard', 'app', 'workflow', 'api', 'page']), + path: z.string().describe('File path for the schema'), + content: z.string().describe('Schema content (JSON/YAML/TypeScript)'), + description: z.string().optional(), + })).optional() + .describe('Generated low-code schema files'), /** * File structure diff --git a/packages/spec/src/ai/runtime-ops.zod.ts b/packages/spec/src/ai/runtime-ops.zod.ts new file mode 100644 index 000000000..bd82d0c66 --- /dev/null +++ b/packages/spec/src/ai/runtime-ops.zod.ts @@ -0,0 +1,679 @@ +import { z } from 'zod'; +import { PluginHealthStatusSchema } from '../system/plugin-lifecycle-advanced.zod'; + +/** + * # Runtime AI Operations (AIOps) Protocol + * + * Defines protocols for AI-powered runtime operations including: + * - Self-healing and automatic recovery + * - Intelligent auto-scaling + * - Anomaly detection and prediction + * - Performance optimization + * - Root cause analysis + */ + +/** + * Anomaly Detection Configuration + * Configuration for detecting anomalies in plugin behavior + */ +export const AnomalyDetectionConfigSchema = z.object({ + /** + * Enable anomaly detection + */ + enabled: z.boolean().default(true), + + /** + * Metrics to monitor + */ + metrics: z.array(z.enum([ + 'cpu-usage', + 'memory-usage', + 'response-time', + 'error-rate', + 'throughput', + 'latency', + 'connection-count', + 'queue-depth', + ])), + + /** + * Detection algorithm + */ + algorithm: z.enum([ + 'statistical', // Statistical thresholds + 'machine-learning', // ML-based detection + 'heuristic', // Rule-based heuristics + 'hybrid', // Combination of methods + ]).default('hybrid'), + + /** + * Sensitivity level + */ + sensitivity: z.enum(['low', 'medium', 'high']).default('medium') + .describe('How aggressively to detect anomalies'), + + /** + * Time window for analysis (seconds) + */ + timeWindow: z.number().int().min(60).default(300) + .describe('Historical data window for anomaly detection'), + + /** + * Confidence threshold (0-100) + */ + confidenceThreshold: z.number().min(0).max(100).default(80) + .describe('Minimum confidence to flag as anomaly'), + + /** + * Alert on detection + */ + alertOnDetection: z.boolean().default(true), +}); + +/** + * Self-Healing Action + * Defines an automated recovery action + */ +export const SelfHealingActionSchema = z.object({ + /** + * Action identifier + */ + id: z.string(), + + /** + * Action type + */ + type: z.enum([ + 'restart', // Restart the plugin + 'scale', // Scale resources + 'rollback', // Rollback to previous version + 'clear-cache', // Clear caches + 'adjust-config', // Adjust configuration + 'execute-script', // Run custom script + 'notify', // Notify administrators + ]), + + /** + * Trigger condition + */ + trigger: z.object({ + /** + * Health status that triggers this action + */ + healthStatus: z.array(PluginHealthStatusSchema).optional(), + + /** + * Anomaly types that trigger this action + */ + anomalyTypes: z.array(z.string()).optional(), + + /** + * Error patterns that trigger this action + */ + errorPatterns: z.array(z.string()).optional(), + + /** + * Custom condition expression + */ + customCondition: z.string().optional() + .describe('Custom trigger condition (e.g., "errorRate > 0.1")'), + }), + + /** + * Action parameters + */ + parameters: z.record(z.string(), z.any()).optional(), + + /** + * Maximum number of attempts + */ + maxAttempts: z.number().int().min(1).default(3), + + /** + * Cooldown period between attempts (seconds) + */ + cooldown: z.number().int().min(0).default(60), + + /** + * Timeout for action execution (seconds) + */ + timeout: z.number().int().min(1).default(300), + + /** + * Require manual approval + */ + requireApproval: z.boolean().default(false), + + /** + * Priority + */ + priority: z.number().int().min(1).default(5) + .describe('Action priority (lower number = higher priority)'), +}); + +/** + * Self-Healing Configuration + * Complete configuration for self-healing capabilities + */ +export const SelfHealingConfigSchema = z.object({ + /** + * Enable self-healing + */ + enabled: z.boolean().default(true), + + /** + * Healing strategy + */ + strategy: z.enum([ + 'conservative', // Only safe, proven actions + 'moderate', // Balanced approach + 'aggressive', // Try more recovery options + ]).default('moderate'), + + /** + * Recovery actions + */ + actions: z.array(SelfHealingActionSchema), + + /** + * Anomaly detection + */ + anomalyDetection: AnomalyDetectionConfigSchema.optional(), + + /** + * Maximum concurrent healing operations + */ + maxConcurrentHealing: z.number().int().min(1).default(1) + .describe('Maximum number of simultaneous healing attempts'), + + /** + * Learning mode + */ + learning: z.object({ + enabled: z.boolean().default(true) + .describe('Learn from successful/failed healing attempts'), + + feedbackLoop: z.boolean().default(true) + .describe('Adjust strategy based on outcomes'), + }).optional(), +}); + +/** + * Auto-Scaling Policy + * Defines how to automatically scale plugin resources + */ +export const AutoScalingPolicySchema = z.object({ + /** + * Enable auto-scaling + */ + enabled: z.boolean().default(false), + + /** + * Scaling metric + */ + metric: z.enum([ + 'cpu-usage', + 'memory-usage', + 'request-rate', + 'response-time', + 'queue-depth', + 'custom', + ]), + + /** + * Custom metric query (when metric is "custom") + */ + customMetric: z.string().optional(), + + /** + * Target value for the metric + */ + targetValue: z.number() + .describe('Desired metric value (e.g., 70 for 70% CPU)'), + + /** + * Scaling bounds + */ + bounds: z.object({ + /** + * Minimum instances + */ + minInstances: z.number().int().min(1).default(1), + + /** + * Maximum instances + */ + maxInstances: z.number().int().min(1).default(10), + + /** + * Minimum resources per instance + */ + minResources: z.object({ + cpu: z.string().optional().describe('CPU limit (e.g., "0.5", "1")'), + memory: z.string().optional().describe('Memory limit (e.g., "512Mi", "1Gi")'), + }).optional(), + + /** + * Maximum resources per instance + */ + maxResources: z.object({ + cpu: z.string().optional(), + memory: z.string().optional(), + }).optional(), + }), + + /** + * Scale up behavior + */ + scaleUp: z.object({ + /** + * Threshold to trigger scale up + */ + threshold: z.number() + .describe('Metric value that triggers scale up'), + + /** + * Stabilization window (seconds) + */ + stabilizationWindow: z.number().int().min(0).default(60) + .describe('How long metric must exceed threshold'), + + /** + * Cooldown period (seconds) + */ + cooldown: z.number().int().min(0).default(300) + .describe('Minimum time between scale-up operations'), + + /** + * Step size + */ + stepSize: z.number().int().min(1).default(1) + .describe('Number of instances to add'), + }), + + /** + * Scale down behavior + */ + scaleDown: z.object({ + /** + * Threshold to trigger scale down + */ + threshold: z.number() + .describe('Metric value that triggers scale down'), + + /** + * Stabilization window (seconds) + */ + stabilizationWindow: z.number().int().min(0).default(300) + .describe('How long metric must be below threshold'), + + /** + * Cooldown period (seconds) + */ + cooldown: z.number().int().min(0).default(600) + .describe('Minimum time between scale-down operations'), + + /** + * Step size + */ + stepSize: z.number().int().min(1).default(1) + .describe('Number of instances to remove'), + }), + + /** + * Predictive scaling + */ + predictive: z.object({ + enabled: z.boolean().default(false) + .describe('Use ML to predict future load'), + + lookAhead: z.number().int().min(60).default(300) + .describe('How far ahead to predict (seconds)'), + + confidence: z.number().min(0).max(100).default(80) + .describe('Minimum confidence for prediction-based scaling'), + }).optional(), +}); + +/** + * Root Cause Analysis Request + * Request for AI to analyze root cause of issues + */ +export const RootCauseAnalysisRequestSchema = z.object({ + /** + * Incident identifier + */ + incidentId: z.string(), + + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Symptoms observed + */ + symptoms: z.array(z.object({ + type: z.string().describe('Symptom type'), + description: z.string(), + severity: z.enum(['low', 'medium', 'high', 'critical']), + timestamp: z.string().datetime(), + })), + + /** + * Time range for analysis + */ + timeRange: z.object({ + start: z.string().datetime(), + end: z.string().datetime(), + }), + + /** + * Include log analysis + */ + analyzeLogs: z.boolean().default(true), + + /** + * Include metric analysis + */ + analyzeMetrics: z.boolean().default(true), + + /** + * Include dependency analysis + */ + analyzeDependencies: z.boolean().default(true), + + /** + * Context information + */ + context: z.record(z.string(), z.any()).optional(), +}); + +/** + * Root Cause Analysis Result + * Result of root cause analysis + */ +export const RootCauseAnalysisResultSchema = z.object({ + /** + * Analysis identifier + */ + analysisId: z.string(), + + /** + * Incident identifier + */ + incidentId: z.string(), + + /** + * Identified root causes + */ + rootCauses: z.array(z.object({ + /** + * Cause identifier + */ + id: z.string(), + + /** + * Description + */ + description: z.string(), + + /** + * Confidence score (0-100) + */ + confidence: z.number().min(0).max(100), + + /** + * Category + */ + category: z.enum([ + 'code-defect', + 'configuration', + 'resource-exhaustion', + 'dependency-failure', + 'network-issue', + 'data-corruption', + 'security-breach', + 'other', + ]), + + /** + * Evidence + */ + evidence: z.array(z.object({ + type: z.enum(['log', 'metric', 'trace', 'event']), + content: z.string(), + timestamp: z.string().datetime().optional(), + })), + + /** + * Impact assessment + */ + impact: z.enum(['low', 'medium', 'high', 'critical']), + + /** + * Recommended actions + */ + recommendations: z.array(z.string()), + })), + + /** + * Contributing factors + */ + contributingFactors: z.array(z.object({ + description: z.string(), + confidence: z.number().min(0).max(100), + })).optional(), + + /** + * Timeline of events + */ + timeline: z.array(z.object({ + timestamp: z.string().datetime(), + event: z.string(), + significance: z.enum(['low', 'medium', 'high']), + })).optional(), + + /** + * Remediation plan + */ + remediation: z.object({ + /** + * Immediate actions + */ + immediate: z.array(z.string()), + + /** + * Short-term fixes + */ + shortTerm: z.array(z.string()), + + /** + * Long-term improvements + */ + longTerm: z.array(z.string()), + }).optional(), + + /** + * Overall confidence in analysis + */ + overallConfidence: z.number().min(0).max(100), + + /** + * Analysis timestamp + */ + timestamp: z.string().datetime(), +}); + +/** + * Performance Optimization Suggestion + * AI-generated performance optimization suggestion + */ +export const PerformanceOptimizationSchema = z.object({ + /** + * Optimization identifier + */ + id: z.string(), + + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Optimization type + */ + type: z.enum([ + 'caching', + 'query-optimization', + 'resource-allocation', + 'code-refactoring', + 'architecture-change', + 'configuration-tuning', + ]), + + /** + * Description + */ + description: z.string(), + + /** + * Expected impact + */ + expectedImpact: z.object({ + /** + * Performance improvement percentage + */ + performanceGain: z.number().min(0).max(100) + .describe('Expected performance improvement (%)'), + + /** + * Resource savings + */ + resourceSavings: z.object({ + cpu: z.number().optional().describe('CPU reduction (%)'), + memory: z.number().optional().describe('Memory reduction (%)'), + network: z.number().optional().describe('Network reduction (%)'), + }).optional(), + + /** + * Cost reduction + */ + costReduction: z.number().optional() + .describe('Estimated cost reduction (%)'), + }), + + /** + * Implementation difficulty + */ + difficulty: z.enum(['trivial', 'easy', 'moderate', 'complex', 'very-complex']), + + /** + * Implementation steps + */ + steps: z.array(z.string()), + + /** + * Risks and considerations + */ + risks: z.array(z.string()).optional(), + + /** + * Confidence score + */ + confidence: z.number().min(0).max(100), + + /** + * Priority + */ + priority: z.enum(['low', 'medium', 'high', 'critical']), +}); + +/** + * AIOps Agent Configuration + * Configuration for AI operations agent + */ +export const AIOpsAgentConfigSchema = z.object({ + /** + * Agent identifier + */ + agentId: z.string(), + + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Self-healing configuration + */ + selfHealing: SelfHealingConfigSchema.optional(), + + /** + * Auto-scaling policies + */ + autoScaling: z.array(AutoScalingPolicySchema).optional(), + + /** + * Continuous monitoring + */ + monitoring: z.object({ + enabled: z.boolean().default(true), + interval: z.number().int().min(1000).default(60000) + .describe('Monitoring interval in milliseconds'), + + /** + * Metrics to collect + */ + metrics: z.array(z.string()).optional(), + }).optional(), + + /** + * Proactive optimization + */ + optimization: z.object({ + enabled: z.boolean().default(true), + + /** + * Scan interval (seconds) + */ + scanInterval: z.number().int().min(3600).default(86400) + .describe('How often to scan for optimization opportunities'), + + /** + * Auto-apply optimizations + */ + autoApply: z.boolean().default(false) + .describe('Automatically apply low-risk optimizations'), + }).optional(), + + /** + * Incident response + */ + incidentResponse: z.object({ + enabled: z.boolean().default(true), + + /** + * Auto-trigger root cause analysis + */ + autoRCA: z.boolean().default(true), + + /** + * Notification channels + */ + notifications: z.array(z.object({ + channel: z.enum(['email', 'slack', 'webhook', 'sms']), + config: z.record(z.string(), z.any()), + })).optional(), + }).optional(), +}); + +// Export types +export type AnomalyDetectionConfig = z.infer; +export type SelfHealingAction = z.infer; +export type SelfHealingConfig = z.infer; +export type AutoScalingPolicy = z.infer; +export type RootCauseAnalysisRequest = z.infer; +export type RootCauseAnalysisResult = z.infer; +export type PerformanceOptimization = z.infer; +export type AIOpsAgentConfig = z.infer; diff --git a/packages/spec/src/hub/marketplace-enhanced.zod.ts b/packages/spec/src/hub/marketplace-enhanced.zod.ts index c844b5bd9..1760a9a89 100644 --- a/packages/spec/src/hub/marketplace-enhanced.zod.ts +++ b/packages/spec/src/hub/marketplace-enhanced.zod.ts @@ -15,8 +15,176 @@ import { PluginVersionMetadataSchema } from '../system/plugin-versioning.zod'; * - Automated installation and updates * - License management * - Revenue sharing for plugin developers + * - Registry federation for hybrid cloud deployments */ +/** + * Registry Sync Policy + * Defines how registries synchronize with upstreams + */ +export const RegistrySyncPolicySchema = z.enum([ + 'manual', // Manual synchronization only + 'auto', // Automatic synchronization + 'proxy', // Proxy requests to upstream without caching +]).describe('Registry synchronization strategy'); + +/** + * Registry Upstream Configuration + * Configuration for upstream registry connection + */ +export const RegistryUpstreamSchema = z.object({ + /** + * Upstream registry URL + */ + url: z.string().url() + .describe('Upstream registry endpoint'), + + /** + * Synchronization policy + */ + syncPolicy: RegistrySyncPolicySchema.default('auto'), + + /** + * Sync interval in seconds (for auto sync) + */ + syncInterval: z.number().int().min(60).optional() + .describe('Auto-sync interval in seconds'), + + /** + * Authentication credentials + */ + auth: z.object({ + type: z.enum(['none', 'basic', 'bearer', 'api-key', 'oauth2']).default('none'), + username: z.string().optional(), + password: z.string().optional(), + token: z.string().optional(), + apiKey: z.string().optional(), + }).optional(), + + /** + * TLS/SSL configuration + */ + tls: z.object({ + enabled: z.boolean().default(true), + verifyCertificate: z.boolean().default(true), + certificate: z.string().optional(), + privateKey: z.string().optional(), + }).optional(), + + /** + * Timeout settings + */ + timeout: z.number().int().min(1000).default(30000) + .describe('Request timeout in milliseconds'), + + /** + * Retry configuration + */ + retry: z.object({ + maxAttempts: z.number().int().min(0).default(3), + backoff: z.enum(['fixed', 'linear', 'exponential']).default('exponential'), + }).optional(), +}); + +/** + * Registry Configuration + * Complete registry configuration supporting federation + */ +export const RegistryConfigSchema = z.object({ + /** + * Registry type + */ + type: z.enum([ + 'public', // Public marketplace (e.g., plugins.objectstack.com) + 'private', // Private enterprise registry + 'hybrid', // Hybrid with upstream federation + ]).describe('Registry deployment type'), + + /** + * Upstream registries (for hybrid/private registries) + */ + upstream: z.array(RegistryUpstreamSchema).optional() + .describe('Upstream registries to sync from or proxy to'), + + /** + * Scopes managed by this registry + */ + scope: z.array(z.string()).optional() + .describe('npm-style scopes managed by this registry (e.g., @my-corp, @enterprise)'), + + /** + * Default scope for new plugins + */ + defaultScope: z.string().optional() + .describe('Default scope prefix for new plugins'), + + /** + * Registry storage configuration + */ + storage: z.object({ + /** + * Storage backend type + */ + backend: z.enum(['local', 's3', 'gcs', 'azure-blob', 'oss']).default('local'), + + /** + * Storage path or bucket name + */ + path: z.string().optional(), + + /** + * Credentials + */ + credentials: z.record(z.string(), z.any()).optional(), + }).optional(), + + /** + * Registry visibility + */ + visibility: z.enum(['public', 'private', 'internal']).default('private') + .describe('Who can access this registry'), + + /** + * Access control + */ + accessControl: z.object({ + /** + * Require authentication for read + */ + requireAuthForRead: z.boolean().default(false), + + /** + * Require authentication for write + */ + requireAuthForWrite: z.boolean().default(true), + + /** + * Allowed users/teams + */ + allowedPrincipals: z.array(z.string()).optional(), + }).optional(), + + /** + * Caching configuration + */ + cache: z.object({ + enabled: z.boolean().default(true), + ttl: z.number().int().min(0).default(3600) + .describe('Cache TTL in seconds'), + maxSize: z.number().int().optional() + .describe('Maximum cache size in bytes'), + }).optional(), + + /** + * Mirroring configuration (for high availability) + */ + mirrors: z.array(z.object({ + url: z.string().url(), + priority: z.number().int().min(1).default(1), + })).optional() + .describe('Mirror registries for redundancy'), +}); + /** * Plugin Category * Categorization for better discovery @@ -679,6 +847,9 @@ export const PluginRevenueSharingSchema = z.object({ }); // Export types +export type RegistrySyncPolicy = z.infer; +export type RegistryUpstream = z.infer; +export type RegistryConfig = z.infer; export type PluginCategory = z.infer; export type PluginTag = z.infer; export type PluginRating = z.infer; diff --git a/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts b/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts index 2117bffee..561088094 100644 --- a/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts +++ b/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts @@ -132,6 +132,60 @@ export const PluginHealthReportSchema = z.object({ })).optional(), }); +/** + * Distributed State Configuration + * Configuration for distributed state management in cluster environments + */ +export const DistributedStateConfigSchema = z.object({ + /** + * Distributed cache provider + */ + provider: z.enum(['redis', 'etcd', 'custom']) + .describe('Distributed state backend provider'), + + /** + * Connection URL or endpoints + */ + endpoints: z.array(z.string()).optional() + .describe('Backend connection endpoints'), + + /** + * Key prefix for namespacing + */ + keyPrefix: z.string().optional() + .describe('Prefix for all keys (e.g., "plugin:my-plugin:")'), + + /** + * Time to live in seconds + */ + ttl: z.number().int().min(0).optional() + .describe('State expiration time in seconds'), + + /** + * Authentication configuration + */ + auth: z.object({ + username: z.string().optional(), + password: z.string().optional(), + token: z.string().optional(), + certificate: z.string().optional(), + }).optional(), + + /** + * Replication settings + */ + replication: z.object({ + enabled: z.boolean().default(true), + minReplicas: z.number().int().min(1).default(1), + }).optional(), + + /** + * Custom provider configuration + */ + customConfig: z.record(z.string(), z.any()).optional() + .describe('Provider-specific configuration'), +}); + /** * Hot Reload Configuration * Controls how plugins handle live updates @@ -163,9 +217,15 @@ export const HotReloadConfigSchema = z.object({ /** * State serialization strategy */ - stateStrategy: z.enum(['memory', 'disk', 'none']).default('memory') + stateStrategy: z.enum(['memory', 'disk', 'distributed', 'none']).default('memory') .describe('How to preserve state during reload'), + /** + * Distributed state configuration (required when stateStrategy is "distributed") + */ + distributedConfig: DistributedStateConfigSchema.optional() + .describe('Configuration for distributed state management'), + /** * Graceful shutdown timeout */ @@ -412,6 +472,7 @@ export const AdvancedPluginLifecycleConfigSchema = z.object({ export type PluginHealthStatus = z.infer; export type PluginHealthCheck = z.infer; export type PluginHealthReport = z.infer; +export type DistributedStateConfig = z.infer; export type HotReloadConfig = z.infer; export type GracefulDegradation = z.infer; export type PluginUpdateStrategy = z.infer; diff --git a/packages/spec/src/system/plugin-security-advanced.zod.ts b/packages/spec/src/system/plugin-security-advanced.zod.ts index a416e1c8e..2b80b5269 100644 --- a/packages/spec/src/system/plugin-security-advanced.zod.ts +++ b/packages/spec/src/system/plugin-security-advanced.zod.ts @@ -158,6 +158,130 @@ export const PermissionSetSchema = z.object({ ]).default('prompt'), }); +/** + * Runtime Configuration + * Defines the execution environment for plugin isolation + */ +export const RuntimeConfigSchema = z.object({ + /** + * Runtime engine type + */ + engine: z.enum([ + 'v8-isolate', // V8 isolate-based isolation (lightweight, fast) + 'wasm', // WebAssembly-based isolation (secure, portable) + 'container', // Container-based isolation (Docker, podman) + 'process', // Process-based isolation (traditional) + ]).default('v8-isolate') + .describe('Execution environment engine'), + + /** + * Engine-specific configuration + */ + engineConfig: z.object({ + /** + * WASM-specific settings (when engine is "wasm") + */ + wasm: z.object({ + /** + * Maximum memory pages (64KB per page) + */ + maxMemoryPages: z.number().int().min(1).max(65536).optional() + .describe('Maximum WASM memory pages (64KB each)'), + + /** + * Instruction execution limit + */ + instructionLimit: z.number().int().min(1).optional() + .describe('Maximum instructions before timeout'), + + /** + * Enable SIMD instructions + */ + enableSimd: z.boolean().default(false) + .describe('Enable WebAssembly SIMD support'), + + /** + * Enable threads + */ + enableThreads: z.boolean().default(false) + .describe('Enable WebAssembly threads'), + + /** + * Enable bulk memory operations + */ + enableBulkMemory: z.boolean().default(true) + .describe('Enable bulk memory operations'), + }).optional(), + + /** + * Container-specific settings (when engine is "container") + */ + container: z.object({ + /** + * Container image + */ + image: z.string().optional() + .describe('Container image to use'), + + /** + * Container runtime + */ + runtime: z.enum(['docker', 'podman', 'containerd']).default('docker'), + + /** + * Resource limits + */ + resources: z.object({ + cpuLimit: z.string().optional().describe('CPU limit (e.g., "0.5", "2")'), + memoryLimit: z.string().optional().describe('Memory limit (e.g., "512m", "1g")'), + }).optional(), + + /** + * Network mode + */ + networkMode: z.enum(['none', 'bridge', 'host']).default('bridge'), + }).optional(), + + /** + * V8 Isolate-specific settings (when engine is "v8-isolate") + */ + v8Isolate: z.object({ + /** + * Heap size limit in MB + */ + heapSizeMb: z.number().int().min(1).optional(), + + /** + * Enable snapshot + */ + enableSnapshot: z.boolean().default(true), + }).optional(), + }).optional(), + + /** + * General resource limits (applies to all engines) + */ + resourceLimits: z.object({ + /** + * Maximum memory in bytes + */ + maxMemory: z.number().int().optional() + .describe('Maximum memory allocation'), + + /** + * Maximum CPU percentage + */ + maxCpu: z.number().min(0).max(100).optional() + .describe('Maximum CPU usage percentage'), + + /** + * Execution timeout in milliseconds + */ + timeout: z.number().int().min(0).optional() + .describe('Maximum execution time'), + }).optional(), +}); + /** * Sandbox Configuration * Defines how plugin is isolated @@ -179,6 +303,12 @@ export const SandboxConfigSchema = z.object({ 'paranoid', // Maximum isolation ]).default('standard'), + /** + * Runtime environment configuration + */ + runtime: RuntimeConfigSchema.optional() + .describe('Execution environment and isolation settings'), + /** * File system access */ @@ -547,6 +677,7 @@ export type PermissionAction = z.infer; export type ResourceType = z.infer; export type Permission = z.infer; export type PermissionSet = z.infer; +export type RuntimeConfig = z.infer; export type SandboxConfig = z.infer; export type SecurityVulnerability = z.infer; export type SecurityScanResult = z.infer; From f5a4002269535581a477631d5a4abcf9d3fc07b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:34:22 +0000 Subject: [PATCH 3/3] Add comprehensive tests for all enterprise plugin enhancements Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../spec/src/ai/plugin-development.test.ts | 289 ++++++++++++ packages/spec/src/ai/runtime-ops.test.ts | 422 ++++++++++++++++++ .../spec/src/hub/marketplace-enhanced.test.ts | 371 +++++++++++++++ .../system/plugin-lifecycle-advanced.test.ts | 66 +++ .../system/plugin-security-advanced.test.ts | 308 +++++++++++++ 5 files changed, 1456 insertions(+) create mode 100644 packages/spec/src/ai/plugin-development.test.ts create mode 100644 packages/spec/src/ai/runtime-ops.test.ts create mode 100644 packages/spec/src/hub/marketplace-enhanced.test.ts create mode 100644 packages/spec/src/system/plugin-security-advanced.test.ts diff --git a/packages/spec/src/ai/plugin-development.test.ts b/packages/spec/src/ai/plugin-development.test.ts new file mode 100644 index 000000000..99ba464ff --- /dev/null +++ b/packages/spec/src/ai/plugin-development.test.ts @@ -0,0 +1,289 @@ +import { describe, expect, it } from 'vitest'; +import { + CodeGenerationRequestSchema, + GeneratedCodeSchema, + AICodeReviewResultSchema, + PluginCompositionRequestSchema, + PluginRecommendationRequestSchema, +} from './plugin-development.zod'; + +describe('AI Plugin Development Schemas', () => { + describe('CodeGenerationRequestSchema', () => { + it('should validate basic code generation request', () => { + const request = { + description: 'Create a plugin to connect to PostgreSQL database', + pluginType: 'driver' as const, + }; + const result = CodeGenerationRequestSchema.parse(request); + expect(result.description).toBe('Create a plugin to connect to PostgreSQL database'); + expect(result.pluginType).toBe('driver'); + expect(result.outputFormat).toBe('source-code'); + expect(result.language).toBe('typescript'); + }); + + it('should validate low-code schema generation request', () => { + const request = { + description: 'Create a CRM app with contacts and deals management', + pluginType: 'app' as const, + outputFormat: 'low-code-schema' as const, + schemaOptions: { + format: 'typescript' as const, + includeExamples: true, + strictValidation: true, + generateUI: true, + generateDataModels: true, + }, + options: { + generateTests: true, + generateDocs: true, + }, + }; + const result = CodeGenerationRequestSchema.parse(request); + expect(result.outputFormat).toBe('low-code-schema'); + expect(result.schemaOptions?.format).toBe('typescript'); + expect(result.schemaOptions?.generateUI).toBe(true); + expect(result.schemaOptions?.generateDataModels).toBe(true); + }); + + it('should validate DSL generation request', () => { + const request = { + description: 'Create a workflow automation plugin', + pluginType: 'automation' as const, + outputFormat: 'dsl' as const, + capabilities: ['flow-execution', 'trigger-management'], + examples: [ + { + input: 'When a new contact is created', + expectedOutput: 'Send welcome email', + description: 'Welcome email automation', + }, + ], + }; + const result = CodeGenerationRequestSchema.parse(request); + expect(result.outputFormat).toBe('dsl'); + expect(result.capabilities).toHaveLength(2); + expect(result.examples).toHaveLength(1); + }); + + it('should validate source code generation with framework preferences', () => { + const request = { + description: 'Create a UI widget for data visualization', + pluginType: 'widget' as const, + outputFormat: 'source-code' as const, + language: 'typescript' as const, + framework: { + runtime: 'browser' as const, + uiFramework: 'react' as const, + testing: 'vitest' as const, + }, + style: { + indentation: '2spaces' as const, + quotes: 'single' as const, + semicolons: true, + trailingComma: true, + }, + options: { + generateTests: true, + generateDocs: true, + targetCoverage: 90, + optimizationLevel: 'aggressive' as const, + }, + }; + const result = CodeGenerationRequestSchema.parse(request); + expect(result.framework?.uiFramework).toBe('react'); + expect(result.style?.quotes).toBe('single'); + expect(result.options?.targetCoverage).toBe(90); + }); + }); + + describe('GeneratedCodeSchema', () => { + it('should validate source code output', () => { + const generated = { + outputFormat: 'source-code' as const, + code: 'export class PostgresDriver implements Driver { ... }', + language: 'typescript', + files: [ + { + path: 'src/index.ts', + content: 'export * from "./driver";', + }, + { + path: 'src/driver.ts', + content: 'export class PostgresDriver { ... }', + }, + ], + tests: [ + { + path: 'src/driver.test.ts', + content: 'describe("PostgresDriver", () => { ... })', + coverage: 85, + }, + ], + confidence: 92, + }; + const result = GeneratedCodeSchema.parse(generated); + expect(result.outputFormat).toBe('source-code'); + expect(result.files).toHaveLength(2); + expect(result.tests).toHaveLength(1); + expect(result.confidence).toBe(92); + }); + + it('should validate low-code schema output', () => { + const generated = { + outputFormat: 'low-code-schema' as const, + schemas: [ + { + type: 'object' as const, + path: 'src/objects/contact.object.ts', + content: 'export const ContactObject = defineObject({ ... })', + description: 'Contact data model', + }, + { + type: 'view' as const, + path: 'src/views/contact-list.view.ts', + content: 'export const ContactListView = defineView({ ... })', + description: 'Contact list view', + }, + { + type: 'dashboard' as const, + path: 'src/dashboards/crm.dashboard.ts', + content: 'export const CRMDashboard = defineDashboard({ ... })', + description: 'CRM dashboard', + }, + ], + files: [ + { + path: 'package.json', + content: '{ "name": "@acme/crm-plugin", ... }', + }, + ], + confidence: 88, + suggestions: [ + 'Consider adding more field validations', + 'Add relationship to Deal object', + ], + }; + const result = GeneratedCodeSchema.parse(generated); + expect(result.outputFormat).toBe('low-code-schema'); + expect(result.schemas).toHaveLength(3); + expect(result.schemas?.[0].type).toBe('object'); + expect(result.schemas?.[1].type).toBe('view'); + expect(result.suggestions).toHaveLength(2); + }); + + it('should validate DSL output', () => { + const generated = { + outputFormat: 'dsl' as const, + files: [ + { + path: 'workflows/welcome-email.flow', + content: 'trigger: contact.created\nactions:\n - send-email\n', + description: 'Welcome email workflow', + }, + ], + confidence: 90, + }; + const result = GeneratedCodeSchema.parse(generated); + expect(result.outputFormat).toBe('dsl'); + expect(result.files).toHaveLength(1); + }); + }); + + describe('AICodeReviewResultSchema', () => { + it('should validate code review result', () => { + const review = { + assessment: 'good' as const, + score: 85, + issues: [ + { + severity: 'warning' as const, + category: 'performance' as const, + file: 'src/driver.ts', + line: 42, + message: 'Consider using connection pooling', + suggestion: 'Implement pg.Pool instead of creating new connections', + autoFixable: false, + }, + { + severity: 'info' as const, + category: 'documentation' as const, + file: 'src/driver.ts', + line: 15, + message: 'Missing JSDoc comment', + autoFixable: true, + autoFix: '/** Query execution method */', + }, + ], + recommendations: [ + { + priority: 'medium' as const, + title: 'Add error handling', + description: 'Implement comprehensive error handling for connection failures', + effort: 'small' as const, + }, + ], + }; + const result = AICodeReviewResultSchema.parse(review); + expect(result.assessment).toBe('good'); + expect(result.score).toBe(85); + expect(result.issues).toHaveLength(2); + }); + }); + + describe('PluginCompositionRequestSchema', () => { + it('should validate plugin composition request', () => { + const request = { + goal: 'Create a complete CRM system with email integration', + availablePlugins: [ + { + pluginId: 'com.objectstack.crm', + version: '1.0.0', + capabilities: ['contact-management', 'deal-tracking'], + }, + { + pluginId: 'com.objectstack.email', + version: '2.0.0', + capabilities: ['send-email', 'email-templates'], + }, + ], + constraints: { + maxPlugins: 3, + requiredPlugins: ['com.objectstack.crm'], + performance: { + maxLatency: 500, + maxMemory: 536870912, // 512MB + }, + }, + optimize: 'reliability' as const, + }; + const result = PluginCompositionRequestSchema.parse(request); + expect(result.goal).toBeDefined(); + expect(result.availablePlugins).toHaveLength(2); + expect(result.constraints?.maxPlugins).toBe(3); + }); + }); + + describe('PluginRecommendationRequestSchema', () => { + it('should validate plugin recommendation request', () => { + const request = { + context: { + installedPlugins: ['com.objectstack.core'], + industry: 'healthcare', + useCases: ['patient-management', 'appointment-scheduling'], + teamSize: 25, + budget: 'medium' as const, + }, + criteria: { + prioritize: 'compatibility' as const, + certifiedOnly: true, + minRating: 4.5, + maxResults: 10, + }, + }; + const result = PluginRecommendationRequestSchema.parse(request); + expect(result.context.industry).toBe('healthcare'); + expect(result.criteria?.certifiedOnly).toBe(true); + expect(result.criteria?.minRating).toBe(4.5); + }); + }); +}); diff --git a/packages/spec/src/ai/runtime-ops.test.ts b/packages/spec/src/ai/runtime-ops.test.ts new file mode 100644 index 000000000..96f34859f --- /dev/null +++ b/packages/spec/src/ai/runtime-ops.test.ts @@ -0,0 +1,422 @@ +import { describe, expect, it } from 'vitest'; +import { + AnomalyDetectionConfigSchema, + SelfHealingActionSchema, + SelfHealingConfigSchema, + AutoScalingPolicySchema, + RootCauseAnalysisRequestSchema, + RootCauseAnalysisResultSchema, + PerformanceOptimizationSchema, + AIOpsAgentConfigSchema, +} from './runtime-ops.zod'; + +describe('Runtime AI Operations (AIOps) Schemas', () => { + describe('AnomalyDetectionConfigSchema', () => { + it('should validate basic anomaly detection config', () => { + const config = { + enabled: true, + metrics: ['cpu-usage' as const, 'memory-usage' as const, 'error-rate' as const], + }; + const result = AnomalyDetectionConfigSchema.parse(config); + expect(result.enabled).toBe(true); + expect(result.metrics).toHaveLength(3); + expect(result.algorithm).toBe('hybrid'); + expect(result.sensitivity).toBe('medium'); + }); + + it('should validate advanced anomaly detection config', () => { + const config = { + enabled: true, + metrics: ['response-time' as const, 'throughput' as const, 'latency' as const], + algorithm: 'machine-learning' as const, + sensitivity: 'high' as const, + timeWindow: 600, + confidenceThreshold: 90, + alertOnDetection: true, + }; + const result = AnomalyDetectionConfigSchema.parse(config); + expect(result.algorithm).toBe('machine-learning'); + expect(result.sensitivity).toBe('high'); + expect(result.timeWindow).toBe(600); + expect(result.confidenceThreshold).toBe(90); + }); + }); + + describe('SelfHealingActionSchema', () => { + it('should validate restart action', () => { + const action = { + id: 'auto-restart', + type: 'restart' as const, + trigger: { + healthStatus: ['failed' as const, 'unhealthy' as const], + }, + maxAttempts: 3, + cooldown: 60, + timeout: 300, + }; + const result = SelfHealingActionSchema.parse(action); + expect(result.type).toBe('restart'); + expect(result.maxAttempts).toBe(3); + expect(result.requireApproval).toBe(false); + expect(result.priority).toBe(5); + }); + + it('should validate scale action with custom condition', () => { + const action = { + id: 'scale-up', + type: 'scale' as const, + trigger: { + customCondition: 'cpuUsage > 80 AND duration > 300', + }, + parameters: { + direction: 'up', + instances: 2, + }, + maxAttempts: 5, + cooldown: 300, + timeout: 600, + requireApproval: true, + priority: 3, + }; + const result = SelfHealingActionSchema.parse(action); + expect(result.type).toBe('scale'); + expect(result.requireApproval).toBe(true); + expect(result.priority).toBe(3); + }); + + it('should validate adjust-config action', () => { + const action = { + id: 'tune-memory', + type: 'adjust-config' as const, + trigger: { + anomalyTypes: ['memory-leak', 'high-memory-usage'], + }, + parameters: { + config: { + maxHeap: 1073741824, // 1GB + gcStrategy: 'aggressive', + }, + }, + }; + const result = SelfHealingActionSchema.parse(action); + expect(result.type).toBe('adjust-config'); + expect(result.trigger.anomalyTypes).toHaveLength(2); + }); + }); + + describe('SelfHealingConfigSchema', () => { + it('should validate basic self-healing config', () => { + const config = { + enabled: true, + actions: [ + { + id: 'restart-on-failure', + type: 'restart' as const, + trigger: { + healthStatus: ['failed' as const], + }, + }, + ], + }; + const result = SelfHealingConfigSchema.parse(config); + expect(result.enabled).toBe(true); + expect(result.strategy).toBe('moderate'); + expect(result.actions).toHaveLength(1); + expect(result.maxConcurrentHealing).toBe(1); + }); + + it('should validate comprehensive self-healing config', () => { + const config = { + enabled: true, + strategy: 'aggressive' as const, + actions: [ + { + id: 'restart', + type: 'restart' as const, + trigger: { healthStatus: ['failed' as const] }, + }, + { + id: 'scale', + type: 'scale' as const, + trigger: { customCondition: 'load > 90' }, + parameters: { instances: 2 }, + }, + ], + anomalyDetection: { + enabled: true, + metrics: ['cpu-usage' as const, 'memory-usage' as const], + algorithm: 'machine-learning' as const, + }, + maxConcurrentHealing: 3, + learning: { + enabled: true, + feedbackLoop: true, + }, + }; + const result = SelfHealingConfigSchema.parse(config); + expect(result.strategy).toBe('aggressive'); + expect(result.actions).toHaveLength(2); + expect(result.anomalyDetection?.enabled).toBe(true); + expect(result.learning?.enabled).toBe(true); + }); + }); + + describe('AutoScalingPolicySchema', () => { + it('should validate CPU-based auto-scaling', () => { + const policy = { + enabled: true, + metric: 'cpu-usage' as const, + targetValue: 70, + bounds: { + minInstances: 2, + maxInstances: 10, + }, + scaleUp: { + threshold: 80, + stabilizationWindow: 60, + cooldown: 300, + stepSize: 2, + }, + scaleDown: { + threshold: 50, + stabilizationWindow: 300, + cooldown: 600, + stepSize: 1, + }, + }; + const result = AutoScalingPolicySchema.parse(policy); + expect(result.metric).toBe('cpu-usage'); + expect(result.targetValue).toBe(70); + expect(result.bounds.minInstances).toBe(2); + expect(result.scaleUp.threshold).toBe(80); + }); + + it('should validate predictive auto-scaling', () => { + const policy = { + enabled: true, + metric: 'request-rate' as const, + targetValue: 1000, + bounds: { + minInstances: 1, + maxInstances: 20, + minResources: { + cpu: '0.5', + memory: '512Mi', + }, + maxResources: { + cpu: '2', + memory: '2Gi', + }, + }, + scaleUp: { + threshold: 1200, + stabilizationWindow: 120, + cooldown: 180, + stepSize: 3, + }, + scaleDown: { + threshold: 800, + stabilizationWindow: 600, + cooldown: 900, + stepSize: 1, + }, + predictive: { + enabled: true, + lookAhead: 600, + confidence: 85, + }, + }; + const result = AutoScalingPolicySchema.parse(policy); + expect(result.predictive?.enabled).toBe(true); + expect(result.predictive?.lookAhead).toBe(600); + expect(result.bounds.minResources?.cpu).toBe('0.5'); + }); + }); + + describe('RootCauseAnalysisRequestSchema', () => { + it('should validate RCA request', () => { + const request = { + incidentId: 'INC-2024-001', + pluginId: 'com.acme.analytics', + symptoms: [ + { + type: 'high-error-rate', + description: 'Error rate increased to 15%', + severity: 'critical' as const, + timestamp: new Date().toISOString(), + }, + { + type: 'slow-response', + description: 'Response time degraded to 5s', + severity: 'high' as const, + timestamp: new Date().toISOString(), + }, + ], + timeRange: { + start: new Date(Date.now() - 3600000).toISOString(), + end: new Date().toISOString(), + }, + analyzeLogs: true, + analyzeMetrics: true, + analyzeDependencies: true, + }; + const result = RootCauseAnalysisRequestSchema.parse(request); + expect(result.incidentId).toBe('INC-2024-001'); + expect(result.symptoms).toHaveLength(2); + expect(result.analyzeLogs).toBe(true); + }); + }); + + describe('RootCauseAnalysisResultSchema', () => { + it('should validate RCA result', () => { + const result = { + analysisId: 'RCA-2024-001', + incidentId: 'INC-2024-001', + rootCauses: [ + { + id: 'cause-1', + description: 'Database connection pool exhausted', + confidence: 95, + category: 'resource-exhaustion' as const, + evidence: [ + { + type: 'log' as const, + content: 'Connection pool timeout after 30s', + timestamp: new Date().toISOString(), + }, + { + type: 'metric' as const, + content: 'active_connections: 100/100', + }, + ], + impact: 'critical' as const, + recommendations: [ + 'Increase connection pool size to 200', + 'Implement connection timeout handling', + 'Add connection pooling metrics', + ], + }, + ], + remediation: { + immediate: ['Restart plugin to clear connections'], + shortTerm: ['Increase pool size', 'Add monitoring'], + longTerm: ['Implement adaptive pooling', 'Add auto-scaling'], + }, + overallConfidence: 92, + timestamp: new Date().toISOString(), + }; + const parsed = RootCauseAnalysisResultSchema.parse(result); + expect(parsed.rootCauses).toHaveLength(1); + expect(parsed.rootCauses[0].confidence).toBe(95); + expect(parsed.remediation?.immediate).toHaveLength(1); + }); + }); + + describe('PerformanceOptimizationSchema', () => { + it('should validate performance optimization suggestion', () => { + const optimization = { + id: 'opt-001', + pluginId: 'com.acme.analytics', + type: 'caching' as const, + description: 'Implement Redis caching for frequently accessed data', + expectedImpact: { + performanceGain: 40, + resourceSavings: { + cpu: 25, + memory: 15, + }, + costReduction: 20, + }, + difficulty: 'moderate' as const, + steps: [ + 'Install Redis adapter', + 'Configure cache TTL policies', + 'Implement cache invalidation', + 'Add cache metrics', + ], + risks: [ + 'Cache invalidation complexity', + 'Additional memory overhead', + ], + confidence: 88, + priority: 'high' as const, + }; + const result = PerformanceOptimizationSchema.parse(optimization); + expect(result.type).toBe('caching'); + expect(result.expectedImpact.performanceGain).toBe(40); + expect(result.steps).toHaveLength(4); + expect(result.priority).toBe('high'); + }); + }); + + describe('AIOpsAgentConfigSchema', () => { + it('should validate complete AIOps agent config', () => { + const config = { + agentId: 'aiops-001', + pluginId: 'com.acme.analytics', + selfHealing: { + enabled: true, + strategy: 'moderate' as const, + actions: [ + { + id: 'restart', + type: 'restart' as const, + trigger: { healthStatus: ['failed' as const] }, + }, + ], + }, + autoScaling: [ + { + enabled: true, + metric: 'cpu-usage' as const, + targetValue: 70, + bounds: { + minInstances: 1, + maxInstances: 5, + }, + scaleUp: { + threshold: 80, + stabilizationWindow: 60, + cooldown: 300, + stepSize: 1, + }, + scaleDown: { + threshold: 50, + stabilizationWindow: 300, + cooldown: 600, + stepSize: 1, + }, + }, + ], + monitoring: { + enabled: true, + interval: 30000, + metrics: ['cpu', 'memory', 'latency'], + }, + optimization: { + enabled: true, + scanInterval: 86400, + autoApply: false, + }, + incidentResponse: { + enabled: true, + autoRCA: true, + notifications: [ + { + channel: 'slack' as const, + config: { + webhook: 'https://hooks.slack.com/...', + }, + }, + ], + }, + }; + const result = AIOpsAgentConfigSchema.parse(config); + expect(result.agentId).toBe('aiops-001'); + expect(result.selfHealing?.enabled).toBe(true); + expect(result.autoScaling).toHaveLength(1); + expect(result.monitoring?.enabled).toBe(true); + expect(result.incidentResponse?.autoRCA).toBe(true); + }); + }); +}); diff --git a/packages/spec/src/hub/marketplace-enhanced.test.ts b/packages/spec/src/hub/marketplace-enhanced.test.ts new file mode 100644 index 000000000..f678599f3 --- /dev/null +++ b/packages/spec/src/hub/marketplace-enhanced.test.ts @@ -0,0 +1,371 @@ +import { describe, expect, it } from 'vitest'; +import { + RegistrySyncPolicySchema, + RegistryUpstreamSchema, + RegistryConfigSchema, + PluginCategorySchema, + PluginLicenseSchema, + PluginMarketplaceListingSchema, + PluginSearchQuerySchema, + PluginInstallationRequestSchema, +} from './marketplace-enhanced.zod'; + +describe('Marketplace Enhanced Schemas', () => { + describe('RegistrySyncPolicySchema', () => { + it('should validate sync policies', () => { + expect(() => RegistrySyncPolicySchema.parse('manual')).not.toThrow(); + expect(() => RegistrySyncPolicySchema.parse('auto')).not.toThrow(); + expect(() => RegistrySyncPolicySchema.parse('proxy')).not.toThrow(); + }); + + it('should reject invalid sync policy', () => { + expect(() => RegistrySyncPolicySchema.parse('invalid')).toThrow(); + }); + }); + + describe('RegistryUpstreamSchema', () => { + it('should validate basic upstream configuration', () => { + const upstream = { + url: 'https://plugins.objectstack.com', + syncPolicy: 'auto' as const, + }; + const result = RegistryUpstreamSchema.parse(upstream); + expect(result.url).toBe('https://plugins.objectstack.com'); + expect(result.syncPolicy).toBe('auto'); + expect(result.timeout).toBe(30000); + }); + + it('should validate upstream with authentication', () => { + const upstream = { + url: 'https://registry.acme.com', + syncPolicy: 'auto' as const, + syncInterval: 3600, + auth: { + type: 'bearer' as const, + token: 'eyJhbGciOiJIUzI1NiIs...', + }, + tls: { + enabled: true, + verifyCertificate: true, + }, + retry: { + maxAttempts: 5, + backoff: 'exponential' as const, + }, + }; + const result = RegistryUpstreamSchema.parse(upstream); + expect(result.auth?.type).toBe('bearer'); + expect(result.syncInterval).toBe(3600); + expect(result.retry?.maxAttempts).toBe(5); + }); + + it('should validate upstream with API key authentication', () => { + const upstream = { + url: 'https://private-registry.example.com', + syncPolicy: 'manual' as const, + auth: { + type: 'api-key' as const, + apiKey: 'sk-1234567890abcdef', + }, + }; + const result = RegistryUpstreamSchema.parse(upstream); + expect(result.auth?.type).toBe('api-key'); + expect(result.auth?.apiKey).toBe('sk-1234567890abcdef'); + }); + }); + + describe('RegistryConfigSchema', () => { + it('should validate public registry', () => { + const config = { + type: 'public' as const, + storage: { + backend: 's3' as const, + path: 'objectstack-plugins', + }, + visibility: 'public' as const, + cache: { + enabled: true, + ttl: 3600, + }, + }; + const result = RegistryConfigSchema.parse(config); + expect(result.type).toBe('public'); + expect(result.visibility).toBe('public'); + }); + + it('should validate private registry with scopes', () => { + const config = { + type: 'private' as const, + scope: ['@acme', '@enterprise'], + defaultScope: '@acme', + storage: { + backend: 'local' as const, + path: '/var/lib/objectstack/plugins', + }, + visibility: 'private' as const, + accessControl: { + requireAuthForRead: true, + requireAuthForWrite: true, + allowedPrincipals: ['team:engineering', 'team:platform'], + }, + }; + const result = RegistryConfigSchema.parse(config); + expect(result.type).toBe('private'); + expect(result.scope).toHaveLength(2); + expect(result.defaultScope).toBe('@acme'); + expect(result.accessControl?.requireAuthForRead).toBe(true); + }); + + it('should validate hybrid registry with federation', () => { + const config = { + type: 'hybrid' as const, + upstream: [ + { + url: 'https://plugins.objectstack.com', + syncPolicy: 'auto' as const, + syncInterval: 7200, + }, + { + url: 'https://npmjs.org', + syncPolicy: 'proxy' as const, + }, + ], + scope: ['@my-company'], + storage: { + backend: 's3' as const, + path: 'my-company-plugins', + }, + visibility: 'internal' as const, + cache: { + enabled: true, + ttl: 7200, + maxSize: 10737418240, // 10GB + }, + mirrors: [ + { + url: 'https://mirror1.example.com', + priority: 1, + }, + { + url: 'https://mirror2.example.com', + priority: 2, + }, + ], + }; + const result = RegistryConfigSchema.parse(config); + expect(result.type).toBe('hybrid'); + expect(result.upstream).toHaveLength(2); + expect(result.upstream?.[0].syncPolicy).toBe('auto'); + expect(result.upstream?.[1].syncPolicy).toBe('proxy'); + expect(result.mirrors).toHaveLength(2); + }); + + it('should validate registry with GCS storage', () => { + const config = { + type: 'private' as const, + storage: { + backend: 'gcs' as const, + path: 'my-bucket/plugins', + credentials: { + projectId: 'my-project', + keyFile: '/path/to/keyfile.json', + }, + }, + }; + const result = RegistryConfigSchema.parse(config); + expect(result.storage?.backend).toBe('gcs'); + }); + }); + + describe('PluginCategorySchema', () => { + it('should validate all plugin categories', () => { + const categories = [ + 'data-integration', + 'analytics', + 'ai-ml', + 'automation', + 'communication', + 'crm', + 'erp', + 'productivity', + 'security', + 'ui-components', + 'utilities', + 'developer-tools', + 'other', + ]; + + categories.forEach(category => { + expect(() => PluginCategorySchema.parse(category)).not.toThrow(); + }); + }); + }); + + describe('PluginLicenseSchema', () => { + it('should validate free license', () => { + const license = { + type: 'free' as const, + spdxId: 'MIT', + commercialUse: true, + attributionRequired: false, + }; + const result = PluginLicenseSchema.parse(license); + expect(result.type).toBe('free'); + expect(result.commercialUse).toBe(true); + }); + + it('should validate freemium license with pricing', () => { + const license = { + type: 'freemium' as const, + pricing: { + freeTier: true, + trialDays: 30, + model: 'per-user' as const, + pricePerUnit: 999, // $9.99 + billingPeriod: 'monthly' as const, + currency: 'USD', + }, + }; + const result = PluginLicenseSchema.parse(license); + expect(result.type).toBe('freemium'); + expect(result.pricing?.trialDays).toBe(30); + expect(result.pricing?.pricePerUnit).toBe(999); + }); + + it('should validate enterprise license', () => { + const license = { + type: 'enterprise' as const, + commercialUse: true, + licenseUrl: 'https://acme.com/license', + }; + const result = PluginLicenseSchema.parse(license); + expect(result.type).toBe('enterprise'); + }); + }); + + describe('PluginSearchQuerySchema', () => { + it('should validate basic search query', () => { + const query = { + query: 'analytics dashboard', + }; + const result = PluginSearchQuerySchema.parse(query); + expect(result.query).toBe('analytics dashboard'); + expect(result.sortOrder).toBe('desc'); + expect(result.page).toBe(1); + expect(result.pageSize).toBe(20); + }); + + it('should validate advanced search query', () => { + const query = { + query: 'data connector', + category: 'data-integration' as const, + tags: ['sql', 'postgres', 'mysql'], + minRating: 4.0, + minQualityScore: 80, + certifiedOnly: true, + freeOnly: false, + sortBy: 'popularity' as const, + sortOrder: 'desc' as const, + page: 2, + pageSize: 50, + }; + const result = PluginSearchQuerySchema.parse(query); + expect(result.category).toBe('data-integration'); + expect(result.tags).toHaveLength(3); + expect(result.minRating).toBe(4.0); + expect(result.certifiedOnly).toBe(true); + }); + }); + + describe('PluginInstallationRequestSchema', () => { + it('should validate basic installation request', () => { + const request = { + pluginId: 'com.acme.analytics', + acceptLicense: true, + }; + const result = PluginInstallationRequestSchema.parse(request); + expect(result.pluginId).toBe('com.acme.analytics'); + expect(result.autoEnable).toBe(true); + expect(result.scope).toBe('global'); + }); + + it('should validate installation with specific version and config', () => { + const request = { + pluginId: 'com.acme.crm-connector', + version: '2.1.0', + config: { + apiKey: 'sk-...', + endpoint: 'https://api.acme.com', + }, + acceptLicense: true, + grantPermissions: ['read-data', 'write-data'], + autoEnable: true, + scope: 'tenant' as const, + tenantId: 'tenant-123', + }; + const result = PluginInstallationRequestSchema.parse(request); + expect(result.version).toBe('2.1.0'); + expect(result.scope).toBe('tenant'); + expect(result.tenantId).toBe('tenant-123'); + expect(result.grantPermissions).toHaveLength(2); + }); + }); + + describe('PluginMarketplaceListingSchema', () => { + it('should validate complete marketplace listing', () => { + const listing = { + pluginId: 'com.objectstack.analytics', + name: 'ObjectStack Analytics', + shortDescription: 'Advanced analytics and reporting for ObjectStack', + description: '# ObjectStack Analytics\n\nComprehensive analytics solution...', + publisher: { + id: 'objectstack', + name: 'ObjectStack Inc.', + website: 'https://objectstack.com', + verified: true, + }, + categories: ['analytics' as const, 'productivity' as const], + tags: [ + { name: 'analytics', category: 'feature' as const }, + { name: 'reporting', category: 'feature' as const }, + ], + versions: [ + { + pluginId: 'com.objectstack.analytics', + version: { + major: 1, + minor: 0, + patch: 0, + }, + versionString: '1.0.0', + releaseDate: new Date().toISOString(), + support: { + status: 'active' as const, + securitySupport: true, + }, + }, + ], + latestVersion: '1.0.0', + icon: 'https://cdn.objectstack.com/plugins/analytics/icon.png', + license: { + type: 'freemium' as const, + pricing: { + freeTier: true, + model: 'per-user' as const, + }, + }, + statistics: { + downloads: 5000, + activeInstallations: 1500, + }, + publishedDate: new Date().toISOString(), + lastUpdated: new Date().toISOString(), + }; + const result = PluginMarketplaceListingSchema.parse(listing); + expect(result.name).toBe('ObjectStack Analytics'); + expect(result.publisher.verified).toBe(true); + expect(result.categories).toHaveLength(2); + expect(result.statistics.downloads).toBe(5000); + }); + }); +}); diff --git a/packages/spec/src/system/plugin-lifecycle-advanced.test.ts b/packages/spec/src/system/plugin-lifecycle-advanced.test.ts index 1632a5834..182278cd9 100644 --- a/packages/spec/src/system/plugin-lifecycle-advanced.test.ts +++ b/packages/spec/src/system/plugin-lifecycle-advanced.test.ts @@ -3,6 +3,7 @@ import { PluginHealthStatusSchema, PluginHealthCheckSchema, PluginHealthReportSchema, + DistributedStateConfigSchema, HotReloadConfigSchema, GracefulDegradationSchema, PluginUpdateStrategySchema, @@ -135,6 +136,71 @@ describe('Plugin Lifecycle Advanced Schemas', () => { const result = HotReloadConfigSchema.parse(config); expect(result).toEqual(config); }); + + it('should validate distributed state strategy', () => { + const config = { + enabled: true, + stateStrategy: 'distributed' as const, + distributedConfig: { + provider: 'redis' as const, + endpoints: ['redis://localhost:6379'], + keyPrefix: 'plugin:my-plugin:', + ttl: 3600, + }, + }; + const result = HotReloadConfigSchema.parse(config); + expect(result.stateStrategy).toBe('distributed'); + expect(result.distributedConfig?.provider).toBe('redis'); + expect(result.distributedConfig?.keyPrefix).toBe('plugin:my-plugin:'); + }); + }); + + describe('DistributedStateConfigSchema', () => { + it('should validate Redis configuration', () => { + const config = { + provider: 'redis' as const, + endpoints: ['redis://localhost:6379', 'redis://localhost:6380'], + keyPrefix: 'objectstack:', + ttl: 7200, + auth: { + username: 'admin', + password: 'secret', + }, + replication: { + enabled: true, + minReplicas: 2, + }, + }; + const result = DistributedStateConfigSchema.parse(config); + expect(result.provider).toBe('redis'); + expect(result.endpoints).toHaveLength(2); + expect(result.ttl).toBe(7200); + }); + + it('should validate Etcd configuration', () => { + const config = { + provider: 'etcd' as const, + endpoints: ['http://localhost:2379'], + auth: { + certificate: '/path/to/cert.pem', + }, + }; + const result = DistributedStateConfigSchema.parse(config); + expect(result.provider).toBe('etcd'); + }); + + it('should validate custom provider configuration', () => { + const config = { + provider: 'custom' as const, + customConfig: { + type: 'consul', + address: 'consul.example.com:8500', + }, + }; + const result = DistributedStateConfigSchema.parse(config); + expect(result.provider).toBe('custom'); + expect(result.customConfig).toBeDefined(); + }); }); describe('GracefulDegradationSchema', () => { diff --git a/packages/spec/src/system/plugin-security-advanced.test.ts b/packages/spec/src/system/plugin-security-advanced.test.ts new file mode 100644 index 000000000..6a3fbf5b5 --- /dev/null +++ b/packages/spec/src/system/plugin-security-advanced.test.ts @@ -0,0 +1,308 @@ +import { describe, expect, it } from 'vitest'; +import { + RuntimeConfigSchema, + SandboxConfigSchema, + PermissionSchema, + PermissionSetSchema, + SecurityPolicySchema, + PluginSecurityManifestSchema, +} from './plugin-security-advanced.zod'; + +describe('Plugin Security Advanced Schemas', () => { + describe('RuntimeConfigSchema', () => { + it('should validate default V8 isolate runtime', () => { + const config = RuntimeConfigSchema.parse({}); + expect(config.engine).toBe('v8-isolate'); + }); + + it('should validate WASM runtime with memory pages', () => { + const config = { + engine: 'wasm' as const, + engineConfig: { + wasm: { + maxMemoryPages: 256, + instructionLimit: 1000000, + enableSimd: true, + enableThreads: false, + enableBulkMemory: true, + }, + }, + resourceLimits: { + maxMemory: 16777216, // 16MB + maxCpu: 50, + timeout: 30000, + }, + }; + const result = RuntimeConfigSchema.parse(config); + expect(result.engine).toBe('wasm'); + expect(result.engineConfig?.wasm?.maxMemoryPages).toBe(256); + expect(result.engineConfig?.wasm?.instructionLimit).toBe(1000000); + expect(result.resourceLimits?.maxMemory).toBe(16777216); + }); + + it('should validate container runtime', () => { + const config = { + engine: 'container' as const, + engineConfig: { + container: { + image: 'objectstack/plugin-runtime:latest', + runtime: 'docker' as const, + resources: { + cpuLimit: '0.5', + memoryLimit: '512m', + }, + networkMode: 'bridge' as const, + }, + }, + }; + const result = RuntimeConfigSchema.parse(config); + expect(result.engine).toBe('container'); + expect(result.engineConfig?.container?.image).toBe('objectstack/plugin-runtime:latest'); + expect(result.engineConfig?.container?.runtime).toBe('docker'); + }); + + it('should validate process runtime', () => { + const config = { + engine: 'process' as const, + resourceLimits: { + maxMemory: 1073741824, // 1GB + maxCpu: 100, + timeout: 60000, + }, + }; + const result = RuntimeConfigSchema.parse(config); + expect(result.engine).toBe('process'); + }); + + it('should validate V8 isolate with custom settings', () => { + const config = { + engine: 'v8-isolate' as const, + engineConfig: { + v8Isolate: { + heapSizeMb: 128, + enableSnapshot: true, + }, + }, + }; + const result = RuntimeConfigSchema.parse(config); + expect(result.engineConfig?.v8Isolate?.heapSizeMb).toBe(128); + }); + }); + + describe('SandboxConfigSchema', () => { + it('should validate sandbox with defaults', () => { + const config = SandboxConfigSchema.parse({}); + expect(config.enabled).toBe(true); + expect(config.level).toBe('standard'); + }); + + it('should validate strict sandbox with WASM runtime', () => { + const config = { + enabled: true, + level: 'strict' as const, + runtime: { + engine: 'wasm' as const, + engineConfig: { + wasm: { + maxMemoryPages: 128, + instructionLimit: 500000, + }, + }, + }, + filesystem: { + mode: 'readonly' as const, + allowedPaths: ['/data/readonly'], + }, + network: { + mode: 'restricted' as const, + allowedHosts: ['api.objectstack.com'], + allowedPorts: [443], + }, + memory: { + maxHeap: 67108864, // 64MB + }, + cpu: { + maxCpuPercent: 25, + maxThreads: 2, + }, + }; + const result = SandboxConfigSchema.parse(config); + expect(result.level).toBe('strict'); + expect(result.runtime?.engine).toBe('wasm'); + expect(result.filesystem?.mode).toBe('readonly'); + }); + + it('should validate paranoid sandbox', () => { + const config = { + enabled: true, + level: 'paranoid' as const, + runtime: { + engine: 'wasm' as const, + }, + filesystem: { + mode: 'none' as const, + }, + network: { + mode: 'none' as const, + }, + process: { + allowSpawn: false, + }, + environment: { + mode: 'none' as const, + }, + }; + const result = SandboxConfigSchema.parse(config); + expect(result.level).toBe('paranoid'); + expect(result.filesystem?.mode).toBe('none'); + expect(result.network?.mode).toBe('none'); + }); + }); + + describe('PermissionSchema', () => { + it('should validate basic permission', () => { + const permission = { + id: 'read-objects', + resource: 'data.object' as const, + actions: ['read' as const], + description: 'Read access to data objects', + }; + const result = PermissionSchema.parse(permission); + expect(result.id).toBe('read-objects'); + expect(result.scope).toBe('plugin'); + expect(result.required).toBe(true); + }); + + it('should validate permission with filter', () => { + const permission = { + id: 'manage-user-records', + resource: 'data.record' as const, + actions: ['read' as const, 'update' as const], + scope: 'user' as const, + filter: { + condition: 'owner = currentUser', + fields: ['name', 'email', 'preferences'], + }, + description: 'Manage user records', + justification: 'Required for user profile management', + }; + const result = PermissionSchema.parse(permission); + expect(result.scope).toBe('user'); + expect(result.filter?.fields).toHaveLength(3); + }); + }); + + describe('PermissionSetSchema', () => { + it('should validate permission set', () => { + const permissionSet = { + permissions: [ + { + id: 'read-data', + resource: 'data.object' as const, + actions: ['read' as const], + description: 'Read data', + }, + ], + groups: [ + { + name: 'data-access', + description: 'Data access permissions', + permissions: ['read-data'], + }, + ], + defaultGrant: 'prompt' as const, + }; + const result = PermissionSetSchema.parse(permissionSet); + expect(result.permissions).toHaveLength(1); + expect(result.groups).toHaveLength(1); + }); + }); + + describe('SecurityPolicySchema', () => { + it('should validate comprehensive security policy', () => { + const policy = { + csp: { + directives: { + 'default-src': ["'self'"], + 'script-src': ["'self'", "'unsafe-inline'"], + }, + reportOnly: false, + }, + cors: { + allowedOrigins: ['https://app.objectstack.com'], + allowedMethods: ['GET', 'POST'], + allowedHeaders: ['Content-Type', 'Authorization'], + allowCredentials: true, + }, + rateLimit: { + enabled: true, + maxRequests: 100, + windowMs: 60000, + strategy: 'sliding' as const, + }, + authentication: { + required: true, + methods: ['jwt' as const, 'api-key' as const], + tokenExpiration: 3600, + }, + encryption: { + dataAtRest: true, + dataInTransit: true, + algorithm: 'AES-256-GCM', + minKeyLength: 256, + }, + auditLog: { + enabled: true, + events: ['auth', 'data-access', 'config-change'], + retention: 90, + }, + }; + const result = SecurityPolicySchema.parse(policy); + expect(result.rateLimit?.enabled).toBe(true); + expect(result.authentication?.required).toBe(true); + expect(result.encryption?.dataAtRest).toBe(true); + }); + }); + + describe('PluginSecurityManifestSchema', () => { + it('should validate complete security manifest', () => { + const manifest = { + pluginId: 'com.acme.analytics', + trustLevel: 'trusted' as const, + permissions: { + permissions: [ + { + id: 'read-analytics', + resource: 'data.object' as const, + actions: ['read' as const], + description: 'Read analytics data', + }, + ], + defaultGrant: 'prompt' as const, + }, + sandbox: { + enabled: true, + level: 'strict' as const, + runtime: { + engine: 'wasm' as const, + engineConfig: { + wasm: { + maxMemoryPages: 256, + }, + }, + }, + }, + codeSigning: { + signed: true, + signature: 'sha256:abc123...', + algorithm: 'RSA-SHA256', + timestamp: new Date().toISOString(), + }, + }; + const result = PluginSecurityManifestSchema.parse(manifest); + expect(result.trustLevel).toBe('trusted'); + expect(result.sandbox.level).toBe('strict'); + expect(result.codeSigning?.signed).toBe(true); + }); + }); +});