diff --git a/MICROKERNEL_IMPROVEMENT_PLAN.md b/MICROKERNEL_IMPROVEMENT_PLAN.md new file mode 100644 index 000000000..420f7c703 --- /dev/null +++ b/MICROKERNEL_IMPROVEMENT_PLAN.md @@ -0,0 +1,620 @@ +# ObjectStack Microkernel and Plugin Architecture Improvement Plan + +## 概述 (Overview) + +本文档详细说明了 ObjectStack 微内核和插件架构的全面改进方案,旨在打造全球最顶尖的企业管理软件平台框架,并推进基于此框架的 AI 自动化开发。 + +This document outlines a comprehensive improvement plan for the ObjectStack microkernel and plugin architecture, aimed at building the world's most advanced enterprise management software platform framework and advancing AI-driven automated development. + +## 目标 (Objectives) + +### 核心目标 (Core Goals) + +1. **世界级插件生态系统** - 构建可与 Salesforce AppExchange、ServiceNow Store 媲美的插件市场 +2. **AI 驱动的开发自动化** - 实现从自然语言到完整插件的全自动生成 +3. **企业级安全与合规** - 达到 SOC 2、ISO 27001 级别的安全标准 +4. **无缝扩展能力** - 支持热插拔、零停机更新、多版本共存 +5. **开发者友好** - 降低插件开发门槛,提升开发效率 10 倍以上 + +## 已实现的协议增强 (Implemented Protocol Enhancements) + +### 1. 高级插件生命周期管理 (Advanced Plugin Lifecycle Management) + +**文件**: `packages/spec/src/system/plugin-lifecycle-advanced.zod.ts` + +#### 新增功能: + +1. **健康监控系统** + - 实时健康检查 + - 自动故障恢复 + - 性能指标监控 + - 降级模式支持 + +2. **热重载能力** + - 零停机更新 + - 状态保持 + - 文件监控 + - 优雅关闭 + +3. **优雅降级** + - 依赖失败处理 + - 功能降级策略 + - 自动恢复机制 + - 离线模式支持 + +4. **更新策略** + - 自动/手动更新 + - 定时更新窗口 + - 滚动更新 + - 自动回滚 + +#### 核心协议: + +```typescript +// 健康状态 +export type PluginHealthStatus = + 'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'; + +// 健康检查配置 +interface PluginHealthCheck { + interval: number; // 检查间隔 + timeout: number; // 超时时间 + failureThreshold: number; // 失败阈值 + autoRestart: boolean; // 自动重启 +} + +// 热重载配置 +interface HotReloadConfig { + enabled: boolean; + watchPatterns: string[]; + preserveState: boolean; + stateStrategy: 'memory' | 'disk' | 'none'; +} +``` + +### 2. 插件版本与兼容性管理 (Plugin Versioning & Compatibility) + +**文件**: `packages/spec/src/system/plugin-versioning.zod.ts` + +#### 新增功能: + +1. **语义化版本控制** + - 完整的 SemVer 支持 + - 版本约束匹配 + - 预发布版本管理 + - 构建元数据 + +2. **兼容性矩阵** + - 版本间兼容性声明 + - 破坏性变更跟踪 + - 迁移路径定义 + - 自动化迁移脚本 + +3. **依赖解析引擎** + - 拓扑排序 + - 冲突检测 + - 自动解决策略 + - 循环依赖检测 + +4. **多版本支持** + - 同时运行多个版本 + - 版本路由规则 + - 灰度发布 + - A/B 测试支持 + +#### 核心协议: + +```typescript +// 兼容性级别 +export type CompatibilityLevel = + 'fully-compatible' | 'backward-compatible' | + 'deprecated-compatible' | 'breaking-changes' | 'incompatible'; + +// 破坏性变更 +interface BreakingChange { + type: 'api-removed' | 'api-renamed' | 'behavior-changed'; + description: string; + migrationGuide: string; + automatedMigration: boolean; +} + +// 依赖冲突 +interface DependencyConflict { + type: 'version-mismatch' | 'circular-dependency' | 'incompatible-versions'; + plugins: Array<{pluginId: string, version: string}>; + resolutions: Resolution[]; +} +``` + +### 3. 插件安全与沙箱 (Plugin Security & Sandboxing) + +**文件**: `packages/spec/src/system/plugin-security-advanced.zod.ts` + +#### 新增功能: + +1. **细粒度权限系统** + - 资源级别权限 + - 操作级别权限 + - 字段级别权限 + - 动态权限评估 + +2. **沙箱隔离** + - 文件系统隔离 + - 网络访问控制 + - 进程限制 + - 内存/CPU 配额 + +3. **安全扫描** + - 代码漏洞扫描 + - 依赖漏洞检测 + - 许可证合规检查 + - CVE 数据库集成 + +4. **安全策略** + - CSP (内容安全策略) + - CORS 策略 + - 速率限制 + - 审计日志 + +#### 核心协议: + +```typescript +// 权限定义 +interface Permission { + resource: ResourceType; + actions: PermissionAction[]; + scope: 'global' | 'tenant' | 'user' | 'resource' | 'plugin'; + filter?: { + resourceIds?: string[]; + condition?: string; + fields?: string[]; + }; +} + +// 沙箱配置 +interface SandboxConfig { + level: 'none' | 'minimal' | 'standard' | 'strict' | 'paranoid'; + filesystem: FilesystemAccess; + network: NetworkAccess; + process: ProcessAccess; + memory: MemoryLimits; +} + +// 安全漏洞 +interface SecurityVulnerability { + cve?: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + affectedVersions: string[]; + fixedIn?: string[]; +} +``` + +### 4. 增强的插件市场 (Enhanced Plugin Marketplace) + +**文件**: `packages/spec/src/hub/marketplace-enhanced.zod.ts` + +#### 新增功能: + +1. **智能发现系统** + - 全文搜索 + - 分类和标签 + - 评分和评论 + - 质量评分 + +2. **认证系统** + - 身份验证 + - 功能测试 + - 安全认证 + - 企业级认证 + - 官方合作伙伴 + +3. **许可证管理** + - 开源许可证 + - 商业许可证 + - 免费试用 + - 订阅模式 + - 企业许可 + +4. **收益分成** + - 开发者收益 + - 平台收益 + - 支付管理 + - 税务处理 + +#### 核心协议: + +```typescript +// 插件市场清单 +interface PluginMarketplaceListing { + pluginId: string; + name: string; + publisher: PublisherInfo; + categories: PluginCategory[]; + ratings: RatingInfo; + quality: QualityMetrics; + certification?: Certification; + license: PluginLicense; + statistics: UsageStatistics; +} + +// 质量指标 +interface PluginQualityMetrics { + codeQuality: number; // 0-100 + testCoverage: number; // 0-100 + documentation: number; // 0-100 + performance: number; // 0-100 + security: number; // 0-100 +} + +// 安装请求 +interface PluginInstallationRequest { + pluginId: string; + version?: string; + config?: Record; + acceptLicense: boolean; + grantPermissions?: string[]; + scope: 'global' | 'tenant' | 'user'; +} +``` + +### 5. AI 驱动的插件开发 (AI-Driven Plugin Development) + +**文件**: `packages/spec/src/ai/plugin-development.zod.ts` + +#### 新增功能: + +1. **代码生成** + - 自然语言到代码 + - 智能脚手架 + - 测试自动生成 + - 文档自动生成 + +2. **AI 代码审查** + - 代码质量分析 + - 安全漏洞检测 + - 性能优化建议 + - 最佳实践检查 + +3. **插件组合** + - 智能插件推荐 + - 自动集成 + - 数据流分析 + - 性能预测 + +4. **开发助手** + - 智能代码补全 + - 问题诊断 + - 优化建议 + - 学习路径推荐 + +#### 核心协议: + +```typescript +// 代码生成请求 +interface CodeGenerationRequest { + description: string; // 自然语言描述 + pluginType: PluginType; // 插件类型 + language: 'typescript' | 'javascript' | 'python'; + capabilities?: string[]; // 需要实现的协议 + examples?: Example[]; // 示例用法 +} + +// 生成的代码 +interface GeneratedCode { + code: string; + files: GeneratedFile[]; + tests?: GeneratedTest[]; + documentation?: Documentation; + quality: QualityMetrics; + confidence: number; // AI 置信度 +} + +// AI 代码审查 +interface AICodeReviewResult { + assessment: 'excellent' | 'good' | 'needs-improvement'; + score: number; // 0-100 + issues: CodeIssue[]; + recommendations: Recommendation[]; + security: SecurityAnalysis; +} + +// 插件推荐 +interface PluginRecommendation { + recommendations: Array<{ + pluginId: string; + score: number; + reasons: string[]; + benefits: string[]; + }>; + combinations?: PluginCombination[]; + learningPath?: LearningStep[]; +} +``` + +## 实施路线图 (Implementation Roadmap) + +### Phase 1: 基础增强 (Foundation Enhancement) ✅ COMPLETED + +- [x] 1.1 高级插件生命周期协议 +- [x] 1.2 插件版本与兼容性协议 +- [x] 1.3 插件安全与沙箱协议 +- [x] 1.4 增强的插件市场协议 +- [x] 1.5 AI 驱动的插件开发协议 + +### Phase 2: 核心实现 (Core Implementation) - NEXT + +#### 2.1 微内核增强 (Estimated: 2-3 weeks) + +**目标**: 实现高级生命周期管理 + +- [ ] 实现健康检查系统 +- [ ] 实现热重载机制 +- [ ] 实现优雅降级 +- [ ] 实现状态保持和恢复 + +**文件修改**: +- `packages/core/src/kernel.ts` +- `packages/core/src/plugin-loader.ts` +- 新增: `packages/core/src/health-monitor.ts` +- 新增: `packages/core/src/hot-reload.ts` + +#### 2.2 依赖解析引擎 (Estimated: 2 weeks) + +**目标**: 实现智能依赖管理 + +- [ ] 实现语义化版本解析 +- [ ] 实现冲突检测和解决 +- [ ] 实现循环依赖检测 +- [ ] 实现多版本支持 + +**新增文件**: +- `packages/core/src/dependency-resolver.ts` +- `packages/core/src/version-manager.ts` + +#### 2.3 安全沙箱 (Estimated: 3 weeks) + +**目标**: 实现插件隔离和安全控制 + +- [ ] 实现权限管理系统 +- [ ] 实现资源访问控制 +- [ ] 实现沙箱运行时 +- [ ] 实现安全扫描集成 + +**新增文件**: +- `packages/core/src/security/permission-manager.ts` +- `packages/core/src/security/sandbox-runtime.ts` +- `packages/core/src/security/security-scanner.ts` + +### Phase 3: 市场和分发 (Marketplace & Distribution) - 4 weeks + +#### 3.1 插件市场服务 + +- [ ] 插件注册表 API +- [ ] 搜索和发现引擎 +- [ ] 评分和评论系统 +- [ ] 质量评分引擎 +- [ ] 认证管理 + +#### 3.2 插件分发系统 + +- [ ] 包管理器集成 +- [ ] 自动安装/更新 +- [ ] 许可证验证 +- [ ] 收益分成系统 + +**新增包**: +- `packages/marketplace-server/` - 市场服务器 +- `packages/plugin-cli/` - 插件 CLI 工具 +- `packages/registry-client/` - 注册表客户端 + +### Phase 4: AI 开发助手 (AI Development Assistant) - 4 weeks + +#### 4.1 代码生成引擎 + +- [ ] 自然语言处理 +- [ ] 代码模板引擎 +- [ ] 测试生成器 +- [ ] 文档生成器 + +#### 4.2 AI 代码审查 + +- [ ] 静态代码分析集成 +- [ ] AI 模型集成 +- [ ] 自动修复建议 +- [ ] 性能分析 + +#### 4.3 智能推荐系统 + +- [ ] 插件推荐引擎 +- [ ] 组合分析器 +- [ ] 学习路径生成 +- [ ] 最佳实践建议 + +**新增包**: +- `packages/ai-codegen/` - AI 代码生成 +- `packages/ai-reviewer/` - AI 代码审查 +- `packages/ai-recommender/` - AI 推荐系统 + +### Phase 5: 文档和工具 (Documentation & Tooling) - 2 weeks + +- [ ] 更新开发者文档 +- [ ] 创建交互式教程 +- [ ] 构建示例插件库 +- [ ] 开发者工具包 +- [ ] VSCode 扩展 + +## 技术栈建议 (Recommended Technology Stack) + +### 核心运行时 + +- **微内核**: Node.js + TypeScript +- **插件隔离**: VM2 / Worker Threads +- **依赖解析**: Semver + Topo-sort +- **健康监控**: Pino + Prometheus + +### 市场服务 + +- **API 服务器**: Hono (已有) + PostgreSQL +- **搜索引擎**: Elasticsearch / MeiliSearch +- **文件存储**: S3 兼容存储 +- **CDN**: CloudFlare / AWS CloudFront + +### AI 服务 + +- **代码生成**: GPT-4 / Claude / 本地 LLM +- **代码分析**: Tree-sitter + AST 分析 +- **向量存储**: Pinecone / Qdrant +- **模型服务**: LangChain / LlamaIndex + +### 开发工具 + +- **CLI**: Commander.js +- **脚手架**: Yeoman / Plop +- **测试**: Vitest (已有) +- **文档**: Fumadocs (已有) + Storybook + +## 性能目标 (Performance Targets) + +### 插件加载 + +- 冷启动: < 100ms +- 热重载: < 50ms +- 依赖解析: < 20ms +- 健康检查: < 10ms + +### 市场服务 + +- 搜索延迟: < 200ms +- API 响应: < 100ms +- 插件下载: > 10 MB/s +- 并发用户: > 10,000 + +### AI 服务 + +- 代码生成: < 30s (简单插件) +- 代码审查: < 10s +- 推荐响应: < 1s + +## 安全目标 (Security Targets) + +- [ ] SOC 2 Type II 合规 +- [ ] ISO 27001 认证 +- [ ] OWASP Top 10 防护 +- [ ] 定期安全审计 +- [ ] 漏洞赏金计划 +- [ ] 零信任架构 + +## 成功指标 (Success Metrics) + +### 开发者体验 + +- 插件开发时间减少 80% +- 文档完整性 > 95% +- 开发者满意度 > 4.5/5 +- 首次发布成功率 > 90% + +### 生态系统 + +- 第一年: 100+ 高质量插件 +- 第二年: 500+ 插件 +- 企业级插件: > 50 +- 月活跃开发者: > 1000 + +### 平台性能 + +- 可用性: 99.9% +- 平均响应时间: < 200ms +- 错误率: < 0.1% +- 插件安装成功率: > 99% + +## 下一步行动 (Next Actions) + +### 立即开始 (Week 1-2) + +1. ✅ 创建协议定义 (已完成) +2. [ ] 创建详细的技术设计文档 +3. [ ] 建立开发环境和 CI/CD +4. [ ] 实现健康检查系统原型 +5. [ ] 实现依赖解析器原型 + +### 短期目标 (Month 1-2) + +1. [ ] 完成微内核增强 +2. [ ] 实现基础安全沙箱 +3. [ ] 发布 alpha 版本 +4. [ ] 收集社区反馈 + +### 中期目标 (Month 3-4) + +1. [ ] 完成市场服务 +2. [ ] 实现 AI 代码生成 +3. [ ] 发布 beta 版本 +4. [ ] 启动合作伙伴计划 + +### 长期目标 (Month 5-6) + +1. [ ] 完成所有功能 +2. [ ] 通过安全审计 +3. [ ] 正式发布 1.0 +4. [ ] 举办开发者大会 + +## 风险评估 (Risk Assessment) + +### 技术风险 + +- **高**: 沙箱安全性 → 缓解: 多层防护、定期审计 +- **中**: AI 代码质量 → 缓解: 人工审查、测试覆盖 +- **中**: 性能瓶颈 → 缓解: 性能测试、优化迭代 + +### 生态风险 + +- **中**: 开发者采用率 → 缓解: 降低门槛、提供激励 +- **低**: 市场竞争 → 优势: 开源、AI 驱动、易用性 + +### 运营风险 + +- **中**: 基础设施成本 → 缓解: 云原生、按需扩展 +- **低**: 合规性 → 缓解: 提前规划、专业咨询 + +## 资源需求 (Resource Requirements) + +### 开发团队 + +- 2-3 核心架构师 +- 3-4 全栈工程师 +- 1-2 AI/ML 工程师 +- 1 DevOps 工程师 +- 1 安全工程师 +- 1 技术文档工程师 + +### 基础设施 + +- 开发环境: GitHub Actions +- 生产环境: AWS/GCP +- 监控: Datadog/New Relic +- 安全: Snyk/GitHub Security + +### 预算估算 + +- 人力成本: 主要投入 +- 基础设施: $2K-5K/月 +- 第三方服务: $1K-3K/月 +- AI API: 按使用付费 + +## 结论 (Conclusion) + +通过实施这套全面的改进方案,ObjectStack 将成为: + +1. **最先进的微内核架构** - 超越现有企业软件平台 +2. **最智能的开发体验** - AI 驱动的全自动化开发 +3. **最安全的插件生态** - 企业级安全和合规 +4. **最活跃的开发者社区** - 全球开发者共建共享 + +这将使 ObjectStack 成为下一代企业管理软件平台的事实标准,引领行业创新。 + +--- + +**文档版本**: 1.0 +**创建日期**: 2026-02-03 +**最后更新**: 2026-02-03 +**维护者**: ObjectStack 核心团队 diff --git a/PLUGIN_ARCHITECTURE_DIAGRAMS.md b/PLUGIN_ARCHITECTURE_DIAGRAMS.md new file mode 100644 index 000000000..aeb42e74b --- /dev/null +++ b/PLUGIN_ARCHITECTURE_DIAGRAMS.md @@ -0,0 +1,513 @@ +# ObjectStack Plugin Ecosystem Architecture + +> **Visual Reference** - Complete architecture diagrams for the enhanced plugin system + +## 📐 System Architecture Overview + +``` +┌────────────────────────────────────────────────────────────────────────┐ +│ ObjectStack Platform Ecosystem │ +│ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ AI Development Layer │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ +│ │ │ AI CodeGen │ │ AI Review │ │ AI Recommend │ │ │ +│ │ │ Service │ │ Service │ │ Engine │ │ │ +│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ Plugin Marketplace & Registry │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ +│ │ │ Discovery │ │ Rating & │ │ Security Scan │ │ │ +│ │ │ & Search │ │ Reviews │ │ & Certification│ │ │ +│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ +│ │ │Installation │ │Distribution │ │ Licensing & │ │ │ +│ │ │ Manager │ │ & Updates │ │ Billing │ │ │ +│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ ObjectKernel (Core) │ │ +│ │ ┌──────────────────────────────────────────────────────────┐ │ │ +│ │ │ Plugin Lifecycle Manager │ │ │ +│ │ │ • Health Monitoring • Hot Reload │ │ │ +│ │ │ • Version Management • Graceful Degradation │ │ │ +│ │ │ • State Preservation • Auto Recovery │ │ │ +│ │ └──────────────────────────────────────────────────────────┘ │ │ +│ │ ┌──────────────────────────────────────────────────────────┐ │ │ +│ │ │ Dependency Resolution Engine │ │ │ +│ │ │ • SemVer Resolver • Conflict Detection │ │ │ +│ │ │ • Topology Sort • Multi-Version Support │ │ │ +│ │ └──────────────────────────────────────────────────────────┘ │ │ +│ │ ┌──────────────────────────────────────────────────────────┐ │ │ +│ │ │ Security & Sandbox │ │ │ +│ │ │ • Permission Manager • Resource Control │ │ │ +│ │ │ • Sandbox Runtime • Vulnerability Scanner │ │ │ +│ │ └──────────────────────────────────────────────────────────┘ │ │ +│ │ ┌──────────────────────────────────────────────────────────┐ │ │ +│ │ │ Service Registry (DI Container) │ │ │ +│ │ │ Event Bus (Hook System) │ │ │ +│ │ │ Logger (Pino-based) │ │ │ +│ │ └──────────────────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ Plugin Layer │ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ +│ │ │ Driver │ │ App │ │ Widget │ │ AI │ ··· │ │ +│ │ │ Plugins │ │ Plugins │ │ Plugins │ │ Agents │ │ │ +│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────┘ +``` + +## 🔄 Plugin Lifecycle State Machine + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Plugin Lifecycle States │ +└──────────────────────────────────────────────────────────────────┘ + + ┌────────────┐ + │ IDLE │ ← Initial state + └─────┬──────┘ + │ kernel.use(plugin) + ↓ + ┌────────────┐ + │ LOADING │ ← Downloading/importing + └─────┬──────┘ + │ validate & check dependencies + ↓ + ┌────────────┐ + │INITIALIZING│ ← Register services, subscribe to hooks + └─────┬──────┘ + │ kernel.bootstrap() + ↓ + ┌────────────┐ + │ STARTING │ ← Connect to databases, start servers + └─────┬──────┘ + │ + ↓ + ┌────────────┐ + │ RUNNING │ ← Normal operation + └──┬──┬──┬───┘ + │ │ │ + │ │ └──→ Health Check ──→ DEGRADED ──→ RECOVERING ──┐ + │ │ ↑ │ + │ └──────→ Hot Reload ────────┼───────────────────────┤ + │ │ │ + │ └───────────────────────┘ + ↓ + ┌────────────┐ + │ STOPPING │ ← Graceful shutdown + └─────┬──────┘ + │ + ↓ + ┌────────────┐ + │ DESTROYED │ ← Clean up resources + └────────────┘ +``` + +## 🔐 Security Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Security Layers │ +└─────────────────────────────────────────────────────────────────┘ + +┌────────────────────────────────────────────────────────────────┐ +│ Layer 1: Plugin Trust & Verification │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Code │ │ Signature │ │ CVE │ │ +│ │ Signing │ │ Validation │ │ Scanning │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└────────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────────┐ +│ Layer 2: Permission Management │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Permission Declaration │ │ +│ │ • Resource Types (data, ui, system, network) │ │ +│ │ • Actions (create, read, update, delete, execute) │ │ +│ │ • Scopes (global, tenant, user, resource) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────────┐ +│ Layer 3: Sandbox Isolation │ +│ ┌───────────────┐ ┌───────────────┐ ┌──────────────┐ │ +│ │ Filesystem │ │ Network │ │ Process │ │ +│ │ Isolation │ │ Control │ │ Limits │ │ +│ │ • Allowed │ │ • Whitelist │ │ • Memory │ │ +│ │ • Denied │ │ • Blacklist │ │ • CPU │ │ +│ └───────────────┘ └───────────────┘ └──────────────┘ │ +└────────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────────┐ +│ Layer 4: Runtime Monitoring │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Audit Log │ │ Anomaly │ │ Rate │ │ +│ │ Collection │ │ Detection │ │ Limiting │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└────────────────────────────────────────────────────────────────┘ +``` + +## 🔄 Dependency Resolution Flow + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Dependency Resolution Algorithm │ +└────────────────────────────────────────────────────────────────┘ + +Input: Plugin List + Version Constraints + ↓ +┌────────────────────────┐ +│ 1. Parse Constraints │ (^1.2.3, ~2.0.0, >=1.0.0) +└──────────┬─────────────┘ + ↓ +┌────────────────────────┐ +│ 2. Build Dep Graph │ (Map: plugin → dependencies) +└──────────┬─────────────┘ + ↓ +┌────────────────────────┐ +│ 3. Detect Cycles │ (DFS cycle detection) +└──────────┬─────────────┘ + ↓ + ┌────────┐ + │ Cycles?│ + └───┬────┘ + │ + Yes ──┴─→ ERROR: Circular dependency + │ + No + ↓ +┌────────────────────────┐ +│ 4. Resolve Versions │ (SemVer constraint solving) +└──────────┬─────────────┘ + ↓ + ┌────────────┐ + │ Conflicts? │ + └────┬───────┘ + │ + Yes ──┴─→ ┌─────────────────────────┐ + │ │ Try Resolution: │ + │ │ • Upgrade │ + │ │ • Downgrade │ + │ │ • Multi-version │ + │ └─────────────────────────┘ + │ ↓ + │ ┌─────────┐ + │ │Resolved?│ + │ └────┬────┘ + │ │ + │ No ──┴─→ ERROR: Unresolvable conflict + │ │ + │ Yes + No │ + │ │ + └────────────────┘ + ↓ +┌────────────────────────┐ +│ 5. Topological Sort │ (Correct init order) +└──────────┬─────────────┘ + ↓ +┌────────────────────────┐ +│ 6. Create Install │ +│ Plan │ +└──────────┬─────────────┘ + ↓ + SUCCESS: Installation order ready +``` + +## 🎨 Plugin Marketplace User Flow + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Plugin Discovery & Installation │ +└────────────────────────────────────────────────────────────────┘ + + ┌─────────────┐ + │ USER │ + └──────┬──────┘ + │ + ↓ + ┌─────────────────────┐ + │ 1. Search/Browse │ ← Categories, Tags, Ratings + └──────┬──────────────┘ + ↓ + ┌─────────────────────┐ + │ 2. View Details │ ← Description, Reviews, Metrics + └──────┬──────────────┘ + │ + ↓ + ┌─────────────────────┐ + │ 3. Check │ ← Security Scan Results + │ Security │ Certification Status + └──────┬──────────────┘ + │ + ↓ + ┌─────────────────────┐ + │ 4. Review │ ← Required Permissions + │ Permissions │ Resource Access + └──────┬──────────────┘ + │ + ┌────┴────┐ + │ Accept? │ + └────┬────┘ + │ Yes + ↓ + ┌─────────────────────┐ + │ 5. Download │ ← Verify Signature + └──────┬──────────────┘ + ↓ + ┌─────────────────────┐ + │ 6. Resolve Deps │ ← Check Compatibility + └──────┬──────────────┘ + │ + ┌────┴────────┐ + │ Conflicts? │ + └────┬────────┘ + │ No + ↓ + ┌─────────────────────┐ + │ 7. Install │ ← Init → Start + └──────┬──────────────┘ + │ + ↓ + ┌─────────────────────┐ + │ 8. Health Check │ ← Verify Running + └──────┬──────────────┘ + │ + ↓ + ┌─────────────────────┐ + │ SUCCESS │ + │ Plugin Active │ + └─────────────────────┘ +``` + +## 🤖 AI Development Workflow + +``` +┌────────────────────────────────────────────────────────────────┐ +│ AI-Driven Plugin Development Pipeline │ +└────────────────────────────────────────────────────────────────┘ + + ┌──────────────────────┐ + │ Natural Language │ "I need a plugin that..." + │ Description │ + └──────────┬───────────┘ + ↓ + ┌──────────────────────┐ + │ AI Code Generator │ + │ ┌────────────────┐ │ + │ │ LLM + Context │ │ → Templates, Examples, Best Practices + │ └────────────────┘ │ + └──────────┬───────────┘ + ↓ + ┌──────────────────────┐ + │ Generated Code │ + │ • Plugin Logic │ + │ • Tests │ + │ • Documentation │ + └──────────┬───────────┘ + ↓ + ┌──────────────────────┐ + │ AI Code Review │ + │ ┌────────────────┐ │ + │ │ Quality Check │ │ → Bug Detection, Security, Performance + │ └────────────────┘ │ + └──────────┬───────────┘ + │ + ┌────┴────┐ + │ Issues? │ + └────┬────┘ + │ No + ↓ + ┌──────────────────────┐ + │ Automated Testing │ → Unit, Integration, E2E + └──────────┬───────────┘ + │ + ┌────┴────┐ + │ Pass? │ + └────┬────┘ + │ Yes + ↓ + ┌──────────────────────┐ + │ Security Scan │ → Vulnerability Check + └──────────┬───────────┘ + │ + ↓ + ┌──────────────────────┐ + │ Build Package │ + └──────────┬───────────┘ + ↓ + ┌──────────────────────┐ + │ Publish to │ + │ Marketplace │ + └──────────────────────┘ +``` + +## 📊 Data Flow: Plugin Communication + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Plugin-to-Plugin Communication │ +└────────────────────────────────────────────────────────────────┘ + + Plugin A ObjectKernel Plugin B + │ │ │ + │ 1. Provide Interface │ │ + ├──────────────────────────→│ │ + │ registerService() │ │ + │ │ │ + │ │ 2. Discover Service │ + │ │←───────────────────────┤ + │ │ getService() │ + │ │ │ + │ │ 3. Return Interface │ + │ ├───────────────────────→│ + │ │ │ + │ 4. Direct Call │ │ + │←──────────────────────────┼────────────────────────┤ + │ service.method() │ │ + │ │ │ + │ 5. Return Result │ │ + ├──────────────────────────→│───────────────────────→│ + │ │ │ + │ │ 6. Emit Event │ + │ │←───────────────────────┤ + │ │ trigger('event') │ + │ │ │ + │ 7. Event Handler │ │ + │←──────────────────────────┤ │ + │ hook('event') │ │ + │ │ │ + +Alternative: Event Bus Pattern + │ │ │ + │ emit('data:change') │ │ + ├──────────────────────────→│ │ + │ ├───┐ │ + │ │ │ Broadcast │ + │ ├───┘ │ + │ │ │ + │ on('data:change') │ on('data:change') │ + │←──────────────────────────┼───────────────────────→│ + │ │ │ +``` + +## 🌐 Multi-Version Support Architecture + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Multi-Version Plugin Execution │ +└────────────────────────────────────────────────────────────────┘ + + Request + ↓ + ┌──────────────────┐ + │ Version Router │ + └────────┬─────────┘ + │ + ┌────────────┼────────────┐ + │ │ │ + ↓ ↓ ↓ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │Plugin │ │Plugin │ │Plugin │ + │v1.0.0 │ │v2.0.0 │ │v3.0.0 │ + │(Stable) │ │(Current) │ │(Beta) │ + └──────────┘ └──────────┘ └──────────┘ + │ │ │ + │ │ │ + ┌─────┴────────────┴────────────┴─────┐ + │ Routing Rules │ + │ • tenant.plan == 'legacy' → v1.0 │ + │ • default → v2.0 │ + │ • user.betaTester == true → v3.0 │ + └──────────────────────────────────────┘ + +Gradual Rollout Example: + Week 1: 90% → v2.0, 10% → v3.0 + Week 2: 70% → v2.0, 30% → v3.0 + Week 3: 50% → v2.0, 50% → v3.0 + Week 4: 0% → v2.0, 100% → v3.0 +``` + +## 📈 Quality Metrics Dashboard + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Plugin Quality Scorecard │ +└────────────────────────────────────────────────────────────────┘ + +Plugin: com.acme.awesome-plugin v2.1.3 +─────────────────────────────────────────────────── + +Code Quality [████████░░] 85/100 +Test Coverage [██████████] 95/100 +Documentation [███████░░░] 72/100 +Performance [█████████░] 91/100 +Security [██████████] 98/100 +Maintainability [████████░░] 83/100 +─────────────────────────────────────────────────── +Overall Score: [████████░░] 87/100 + +Certification: ✓ Verified ✓ Tested ✓ Enterprise + +Downloads: 15,234 | Rating: ★★★★☆ (4.3/5) +Active Installations: 8,421 | Last Updated: 2d ago + +Security Scan: ✓ No vulnerabilities +License: MIT (Commercial Use Allowed) +Support: Response Time < 24h +─────────────────────────────────────────────────── +``` + +## 🔮 Future Architecture Extensions + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Roadmap: Future Features │ +└────────────────────────────────────────────────────────────────┘ + +Phase 4: Advanced Features (Q2 2026) +├── Plugin Streaming & Real-time Sync +│ ├── WebSocket-based communication +│ ├── Event streaming between plugins +│ └── Real-time state synchronization +│ +├── Plugin Observability Stack +│ ├── Distributed tracing (OpenTelemetry) +│ ├── Performance profiling +│ └── Dependency visualization +│ +└── Cross-Platform Support + ├── Browser runtime (WebAssembly) + ├── Mobile plugins (React Native) + └── Edge/serverless deployment + +Phase 5: Enterprise Features (Q3 2026) +├── Advanced Governance +│ ├── Approval workflows +│ ├── Change impact analysis +│ └── Rollback automation +│ +├── Compliance & Auditing +│ ├── SOC 2 compliance +│ ├── Audit trail +│ └── Compliance reporting +│ +└── Advanced Economics + ├── Usage-based billing + ├── Plugin monetization + └── Developer revenue sharing +``` + +--- + +**Document Version**: 1.0 +**Created**: 2026-02-03 +**Maintained by**: ObjectStack Architecture Team +**Related**: MICROKERNEL_IMPROVEMENT_PLAN.md, ARCHITECTURE.md diff --git a/content/docs/references/ai/index.mdx b/content/docs/references/ai/index.mdx index a8ec953bf..37920a4b7 100644 --- a/content/docs/references/ai/index.mdx +++ b/content/docs/references/ai/index.mdx @@ -17,7 +17,9 @@ 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 5c810803b..cd6ee502e 100644 --- a/content/docs/references/ai/meta.json +++ b/content/docs/references/ai/meta.json @@ -10,7 +10,9 @@ "model-registry", "nlq", "orchestration", + "plugin-development", "predictive", - "rag-pipeline" + "rag-pipeline", + "runtime-ops" ] } \ No newline at end of file diff --git a/content/docs/references/hub/index.mdx b/content/docs/references/hub/index.mdx index f93dfa946..01af974d2 100644 --- a/content/docs/references/hub/index.mdx +++ b/content/docs/references/hub/index.mdx @@ -12,6 +12,7 @@ This section contains all protocol schemas for the hub layer of ObjectStack. + diff --git a/content/docs/references/hub/meta.json b/content/docs/references/hub/meta.json index dbfba471a..65bec6e43 100644 --- a/content/docs/references/hub/meta.json +++ b/content/docs/references/hub/meta.json @@ -5,6 +5,7 @@ "hub-federation", "license", "marketplace", + "marketplace-enhanced", "plugin-registry", "plugin-security", "space", diff --git a/content/docs/references/permission/permission.mdx b/content/docs/references/permission/permission.mdx index 56c69a0a6..6126ba609 100644 --- a/content/docs/references/permission/permission.mdx +++ b/content/docs/references/permission/permission.mdx @@ -12,8 +12,8 @@ description: Permission protocol schemas ## TypeScript Usage ```typescript -import { FieldPermissionSchema, ObjectPermissionSchema, PermissionSetSchema } from '@objectstack/spec/permission'; -import type { FieldPermission, ObjectPermission, PermissionSet } from '@objectstack/spec/permission'; +import { FieldPermissionSchema, ObjectPermissionSchema } from '@objectstack/spec/permission'; +import type { FieldPermission, ObjectPermission } from '@objectstack/spec/permission'; // Validate data const result = FieldPermissionSchema.parse(data); @@ -27,7 +27,3 @@ const result = FieldPermissionSchema.parse(data); ## ObjectPermission ---- - -## PermissionSet - diff --git a/content/docs/references/system/index.mdx b/content/docs/references/system/index.mdx index 5622dad0a..b04e93918 100644 --- a/content/docs/references/system/index.mdx +++ b/content/docs/references/system/index.mdx @@ -31,9 +31,12 @@ 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 1cc5eddb2..aa9314d92 100644 --- a/content/docs/references/system/meta.json +++ b/content/docs/references/system/meta.json @@ -24,9 +24,12 @@ "object-storage", "plugin", "plugin-capability", + "plugin-lifecycle-advanced", "plugin-lifecycle-events", "plugin-loading", + "plugin-security-advanced", "plugin-validator", + "plugin-versioning", "search-engine", "service-registry", "startup-orchestrator", diff --git a/packages/spec/json-schema/ai/AICodeReviewResult.json b/packages/spec/json-schema/ai/AICodeReviewResult.json new file mode 100644 index 000000000..6fd1551d7 --- /dev/null +++ b/packages/spec/json-schema/ai/AICodeReviewResult.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/AICodeReviewResult", + "definitions": { + "AICodeReviewResult": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ 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/CodeGenerationRequest.json b/packages/spec/json-schema/ai/CodeGenerationRequest.json new file mode 100644 index 000000000..01f7e6eac --- /dev/null +++ b/packages/spec/json-schema/ai/CodeGenerationRequest.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/CodeGenerationRequest", + "definitions": { + "CodeGenerationRequest": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/GeneratedCode.json b/packages/spec/json-schema/ai/GeneratedCode.json new file mode 100644 index 000000000..8ca55ffd5 --- /dev/null +++ b/packages/spec/json-schema/ai/GeneratedCode.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/GeneratedCode", + "definitions": { + "GeneratedCode": {} + }, + "$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/PluginCompositionRequest.json b/packages/spec/json-schema/ai/PluginCompositionRequest.json new file mode 100644 index 000000000..dda2a6eec --- /dev/null +++ b/packages/spec/json-schema/ai/PluginCompositionRequest.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginCompositionRequest", + "definitions": { + "PluginCompositionRequest": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/PluginCompositionResult.json b/packages/spec/json-schema/ai/PluginCompositionResult.json new file mode 100644 index 000000000..07729d304 --- /dev/null +++ b/packages/spec/json-schema/ai/PluginCompositionResult.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginCompositionResult", + "definitions": { + "PluginCompositionResult": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/PluginRecommendation.json b/packages/spec/json-schema/ai/PluginRecommendation.json new file mode 100644 index 000000000..cdc411cd3 --- /dev/null +++ b/packages/spec/json-schema/ai/PluginRecommendation.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginRecommendation", + "definitions": { + "PluginRecommendation": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/PluginRecommendationRequest.json b/packages/spec/json-schema/ai/PluginRecommendationRequest.json new file mode 100644 index 000000000..e82992eb0 --- /dev/null +++ b/packages/spec/json-schema/ai/PluginRecommendationRequest.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginRecommendationRequest", + "definitions": { + "PluginRecommendationRequest": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/PluginScaffoldingTemplate.json b/packages/spec/json-schema/ai/PluginScaffoldingTemplate.json new file mode 100644 index 000000000..b2022b799 --- /dev/null +++ b/packages/spec/json-schema/ai/PluginScaffoldingTemplate.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginScaffoldingTemplate", + "definitions": { + "PluginScaffoldingTemplate": {} + }, + "$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/MarketplaceQualityMetrics.json b/packages/spec/json-schema/hub/MarketplaceQualityMetrics.json new file mode 100644 index 000000000..86823d0b0 --- /dev/null +++ b/packages/spec/json-schema/hub/MarketplaceQualityMetrics.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/MarketplaceQualityMetrics", + "definitions": { + "MarketplaceQualityMetrics": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginCategory.json b/packages/spec/json-schema/hub/PluginCategory.json new file mode 100644 index 000000000..41be7dda6 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginCategory.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginCategory", + "definitions": { + "PluginCategory": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginCertification.json b/packages/spec/json-schema/hub/PluginCertification.json new file mode 100644 index 000000000..f60338142 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginCertification.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginCertification", + "definitions": { + "PluginCertification": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginInstallationRequest.json b/packages/spec/json-schema/hub/PluginInstallationRequest.json new file mode 100644 index 000000000..0a8efcc8d --- /dev/null +++ b/packages/spec/json-schema/hub/PluginInstallationRequest.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginInstallationRequest", + "definitions": { + "PluginInstallationRequest": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginInstallationStatus.json b/packages/spec/json-schema/hub/PluginInstallationStatus.json new file mode 100644 index 000000000..2b0900abd --- /dev/null +++ b/packages/spec/json-schema/hub/PluginInstallationStatus.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginInstallationStatus", + "definitions": { + "PluginInstallationStatus": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginLicense.json b/packages/spec/json-schema/hub/PluginLicense.json new file mode 100644 index 000000000..c600af485 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginLicense.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginLicense", + "definitions": { + "PluginLicense": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginMarketplaceListing.json b/packages/spec/json-schema/hub/PluginMarketplaceListing.json new file mode 100644 index 000000000..1a40e5083 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginMarketplaceListing.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginMarketplaceListing", + "definitions": { + "PluginMarketplaceListing": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginRating.json b/packages/spec/json-schema/hub/PluginRating.json new file mode 100644 index 000000000..9cf6b7f75 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginRating.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginRating", + "definitions": { + "PluginRating": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginRevenueSharing.json b/packages/spec/json-schema/hub/PluginRevenueSharing.json new file mode 100644 index 000000000..5981dcd99 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginRevenueSharing.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginRevenueSharing", + "definitions": { + "PluginRevenueSharing": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginSearchQuery.json b/packages/spec/json-schema/hub/PluginSearchQuery.json new file mode 100644 index 000000000..041ffe3e7 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginSearchQuery.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginSearchQuery", + "definitions": { + "PluginSearchQuery": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/hub/PluginTag.json b/packages/spec/json-schema/hub/PluginTag.json new file mode 100644 index 000000000..c3a2fea14 --- /dev/null +++ b/packages/spec/json-schema/hub/PluginTag.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginTag", + "definitions": { + "PluginTag": {} + }, + "$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/AdvancedPluginLifecycleConfig.json b/packages/spec/json-schema/system/AdvancedPluginLifecycleConfig.json new file mode 100644 index 000000000..0eb0cbdd3 --- /dev/null +++ b/packages/spec/json-schema/system/AdvancedPluginLifecycleConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/AdvancedPluginLifecycleConfig", + "definitions": { + "AdvancedPluginLifecycleConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/BreakingChange.json b/packages/spec/json-schema/system/BreakingChange.json new file mode 100644 index 000000000..392961ec1 --- /dev/null +++ b/packages/spec/json-schema/system/BreakingChange.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/BreakingChange", + "definitions": { + "BreakingChange": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/CompatibilityLevel.json b/packages/spec/json-schema/system/CompatibilityLevel.json new file mode 100644 index 000000000..2e7906e0e --- /dev/null +++ b/packages/spec/json-schema/system/CompatibilityLevel.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/CompatibilityLevel", + "definitions": { + "CompatibilityLevel": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/CompatibilityMatrixEntry.json b/packages/spec/json-schema/system/CompatibilityMatrixEntry.json new file mode 100644 index 000000000..ff86f4f18 --- /dev/null +++ b/packages/spec/json-schema/system/CompatibilityMatrixEntry.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/CompatibilityMatrixEntry", + "definitions": { + "CompatibilityMatrixEntry": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/DependencyConflict.json b/packages/spec/json-schema/system/DependencyConflict.json new file mode 100644 index 000000000..52b895f16 --- /dev/null +++ b/packages/spec/json-schema/system/DependencyConflict.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/DependencyConflict", + "definitions": { + "DependencyConflict": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/DependencyResolutionResult.json b/packages/spec/json-schema/system/DependencyResolutionResult.json new file mode 100644 index 000000000..f6124c225 --- /dev/null +++ b/packages/spec/json-schema/system/DependencyResolutionResult.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/DependencyResolutionResult", + "definitions": { + "DependencyResolutionResult": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/DeprecationNotice.json b/packages/spec/json-schema/system/DeprecationNotice.json new file mode 100644 index 000000000..ffe1b4b14 --- /dev/null +++ b/packages/spec/json-schema/system/DeprecationNotice.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/DeprecationNotice", + "definitions": { + "DeprecationNotice": {} + }, + "$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/GracefulDegradation.json b/packages/spec/json-schema/system/GracefulDegradation.json new file mode 100644 index 000000000..18c0fd4f3 --- /dev/null +++ b/packages/spec/json-schema/system/GracefulDegradation.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/GracefulDegradation", + "definitions": { + "GracefulDegradation": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/HotReloadConfig.json b/packages/spec/json-schema/system/HotReloadConfig.json new file mode 100644 index 000000000..c53bccf91 --- /dev/null +++ b/packages/spec/json-schema/system/HotReloadConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/HotReloadConfig", + "definitions": { + "HotReloadConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/MultiVersionSupport.json b/packages/spec/json-schema/system/MultiVersionSupport.json new file mode 100644 index 000000000..54df6123c --- /dev/null +++ b/packages/spec/json-schema/system/MultiVersionSupport.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/MultiVersionSupport", + "definitions": { + "MultiVersionSupport": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/Permission.json b/packages/spec/json-schema/system/Permission.json new file mode 100644 index 000000000..8776adc0a --- /dev/null +++ b/packages/spec/json-schema/system/Permission.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/Permission", + "definitions": { + "Permission": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PermissionAction.json b/packages/spec/json-schema/system/PermissionAction.json new file mode 100644 index 000000000..bc50ae7b0 --- /dev/null +++ b/packages/spec/json-schema/system/PermissionAction.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PermissionAction", + "definitions": { + "PermissionAction": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PermissionScope.json b/packages/spec/json-schema/system/PermissionScope.json new file mode 100644 index 000000000..968384cc4 --- /dev/null +++ b/packages/spec/json-schema/system/PermissionScope.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PermissionScope", + "definitions": { + "PermissionScope": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PermissionSet.json b/packages/spec/json-schema/system/PermissionSet.json new file mode 100644 index 000000000..f623e1618 --- /dev/null +++ b/packages/spec/json-schema/system/PermissionSet.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PermissionSet", + "definitions": { + "PermissionSet": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginCompatibilityMatrix.json b/packages/spec/json-schema/system/PluginCompatibilityMatrix.json new file mode 100644 index 000000000..d42ea126d --- /dev/null +++ b/packages/spec/json-schema/system/PluginCompatibilityMatrix.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginCompatibilityMatrix", + "definitions": { + "PluginCompatibilityMatrix": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginHealthCheck.json b/packages/spec/json-schema/system/PluginHealthCheck.json new file mode 100644 index 000000000..b5c2c759f --- /dev/null +++ b/packages/spec/json-schema/system/PluginHealthCheck.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginHealthCheck", + "definitions": { + "PluginHealthCheck": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginHealthReport.json b/packages/spec/json-schema/system/PluginHealthReport.json new file mode 100644 index 000000000..826868843 --- /dev/null +++ b/packages/spec/json-schema/system/PluginHealthReport.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginHealthReport", + "definitions": { + "PluginHealthReport": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginHealthStatus.json b/packages/spec/json-schema/system/PluginHealthStatus.json new file mode 100644 index 000000000..173e4969a --- /dev/null +++ b/packages/spec/json-schema/system/PluginHealthStatus.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginHealthStatus", + "definitions": { + "PluginHealthStatus": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginSecurityManifest.json b/packages/spec/json-schema/system/PluginSecurityManifest.json new file mode 100644 index 000000000..d8cf48e1a --- /dev/null +++ b/packages/spec/json-schema/system/PluginSecurityManifest.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginSecurityManifest", + "definitions": { + "PluginSecurityManifest": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginStateSnapshot.json b/packages/spec/json-schema/system/PluginStateSnapshot.json new file mode 100644 index 000000000..81c8c5769 --- /dev/null +++ b/packages/spec/json-schema/system/PluginStateSnapshot.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginStateSnapshot", + "definitions": { + "PluginStateSnapshot": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginTrustLevel.json b/packages/spec/json-schema/system/PluginTrustLevel.json new file mode 100644 index 000000000..3da32225a --- /dev/null +++ b/packages/spec/json-schema/system/PluginTrustLevel.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginTrustLevel", + "definitions": { + "PluginTrustLevel": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginUpdateStrategy.json b/packages/spec/json-schema/system/PluginUpdateStrategy.json new file mode 100644 index 000000000..0e9ff55aa --- /dev/null +++ b/packages/spec/json-schema/system/PluginUpdateStrategy.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginUpdateStrategy", + "definitions": { + "PluginUpdateStrategy": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/PluginVersionMetadata.json b/packages/spec/json-schema/system/PluginVersionMetadata.json new file mode 100644 index 000000000..161bf2119 --- /dev/null +++ b/packages/spec/json-schema/system/PluginVersionMetadata.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/PluginVersionMetadata", + "definitions": { + "PluginVersionMetadata": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/ResourceType.json b/packages/spec/json-schema/system/ResourceType.json new file mode 100644 index 000000000..19b636dc4 --- /dev/null +++ b/packages/spec/json-schema/system/ResourceType.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/ResourceType", + "definitions": { + "ResourceType": {} + }, + "$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/json-schema/system/SandboxConfig.json b/packages/spec/json-schema/system/SandboxConfig.json new file mode 100644 index 000000000..ac89d399d --- /dev/null +++ b/packages/spec/json-schema/system/SandboxConfig.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SandboxConfig", + "definitions": { + "SandboxConfig": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SecurityPolicy.json b/packages/spec/json-schema/system/SecurityPolicy.json new file mode 100644 index 000000000..4512f9d46 --- /dev/null +++ b/packages/spec/json-schema/system/SecurityPolicy.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SecurityPolicy", + "definitions": { + "SecurityPolicy": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SecurityScanResult.json b/packages/spec/json-schema/system/SecurityScanResult.json new file mode 100644 index 000000000..801b4cdd2 --- /dev/null +++ b/packages/spec/json-schema/system/SecurityScanResult.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SecurityScanResult", + "definitions": { + "SecurityScanResult": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SecurityVulnerability.json b/packages/spec/json-schema/system/SecurityVulnerability.json new file mode 100644 index 000000000..9f1121de5 --- /dev/null +++ b/packages/spec/json-schema/system/SecurityVulnerability.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SecurityVulnerability", + "definitions": { + "SecurityVulnerability": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/SemanticVersion.json b/packages/spec/json-schema/system/SemanticVersion.json new file mode 100644 index 000000000..2a906e9e2 --- /dev/null +++ b/packages/spec/json-schema/system/SemanticVersion.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/SemanticVersion", + "definitions": { + "SemanticVersion": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/system/VersionConstraint.json b/packages/spec/json-schema/system/VersionConstraint.json new file mode 100644 index 000000000..7784609f3 --- /dev/null +++ b/packages/spec/json-schema/system/VersionConstraint.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/VersionConstraint", + "definitions": { + "VersionConstraint": {} + }, + "$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 db4a5b4d5..8350c0044 100644 --- a/packages/spec/src/ai/index.ts +++ b/packages/spec/src/ai/index.ts @@ -11,11 +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.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/plugin-development.zod.ts b/packages/spec/src/ai/plugin-development.zod.ts new file mode 100644 index 000000000..6e4c5d9e1 --- /dev/null +++ b/packages/spec/src/ai/plugin-development.zod.ts @@ -0,0 +1,660 @@ +import { z } from 'zod'; + +/** + * # AI-Driven Plugin Development Protocol + * + * Defines protocols for AI-powered plugin development including: + * - Natural language to code generation + * - Intelligent code scaffolding + * - Automated testing and validation + * - AI-powered code review and optimization + * - Plugin composition and recommendation + */ + +/** + * Code Generation Request + * Request for AI to generate plugin code + */ +export const CodeGenerationRequestSchema = z.object({ + /** + * Natural language description of desired functionality + */ + description: z.string().describe('What the plugin should do'), + + /** + * Plugin type to generate + */ + pluginType: z.enum([ + 'driver', // Data driver plugin + 'app', // Application plugin + 'widget', // UI widget + 'integration', // External integration + 'automation', // Automation/workflow + 'analytics', // Analytics plugin + 'ai-agent', // AI agent plugin + 'custom', // Custom plugin type + ]), + + /** + * 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'), + + /** + * Framework preferences + */ + framework: z.object({ + runtime: z.enum(['node', 'browser', 'edge', 'universal']).optional(), + uiFramework: z.enum(['react', 'vue', 'svelte', 'none']).optional(), + testing: z.enum(['vitest', 'jest', 'mocha', 'none']).optional(), + }).optional(), + + /** + * Required capabilities + */ + capabilities: z.array(z.string()).optional().describe('Protocol IDs to implement'), + + /** + * Dependencies + */ + dependencies: z.array(z.string()).optional().describe('Required plugin IDs'), + + /** + * Example usage (helps AI understand intent) + */ + examples: z.array(z.object({ + input: z.string(), + expectedOutput: z.string(), + description: z.string().optional(), + })).optional(), + + /** + * Code style preferences (for source-code format) + */ + style: z.object({ + indentation: z.enum(['tab', '2spaces', '4spaces']).default('2spaces'), + quotes: z.enum(['single', 'double']).default('single'), + semicolons: z.boolean().default(true), + 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 + */ + context: z.object({ + /** + * Existing code to extend + */ + existingCode: z.string().optional(), + + /** + * Related documentation URLs + */ + documentationUrls: z.array(z.string()).optional(), + + /** + * Similar plugins for reference + */ + referencePlugins: z.array(z.string()).optional(), + }).optional(), + + /** + * Generation options + */ + options: z.object({ + /** + * Include tests + */ + generateTests: z.boolean().default(true), + + /** + * Include documentation + */ + generateDocs: z.boolean().default(true), + + /** + * Include examples + */ + generateExamples: z.boolean().default(true), + + /** + * Code coverage target + */ + targetCoverage: z.number().min(0).max(100).default(80), + + /** + * Optimization level + */ + optimizationLevel: z.enum(['none', 'basic', 'aggressive']).default('basic'), + }).optional(), +}); + +/** + * Generated Code + * Result of code generation + */ +export const GeneratedCodeSchema = z.object({ + /** + * Output format used + */ + outputFormat: z.enum(['source-code', 'low-code-schema', 'dsl']), + + /** + * Main plugin code (for source-code format) + */ + 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 + */ + files: z.array(z.object({ + path: z.string(), + content: z.string(), + description: z.string().optional(), + })), + + /** + * Generated tests + */ + tests: z.array(z.object({ + path: z.string(), + content: z.string(), + coverage: z.number().min(0).max(100).optional(), + })).optional(), + + /** + * Documentation + */ + documentation: z.object({ + readme: z.string().optional(), + api: z.string().optional(), + usage: z.string().optional(), + }).optional(), + + /** + * Package metadata + */ + package: z.object({ + name: z.string(), + version: z.string(), + dependencies: z.record(z.string(), z.string()).optional(), + devDependencies: z.record(z.string(), z.string()).optional(), + }).optional(), + + /** + * Quality metrics + */ + quality: z.object({ + complexity: z.number().optional().describe('Cyclomatic complexity'), + maintainability: z.number().min(0).max(100).optional(), + testCoverage: z.number().min(0).max(100).optional(), + lintScore: z.number().min(0).max(100).optional(), + }).optional(), + + /** + * AI confidence score + */ + confidence: z.number().min(0).max(100).describe('AI confidence in generated code'), + + /** + * Suggestions for improvement + */ + suggestions: z.array(z.string()).optional(), + + /** + * Warnings or caveats + */ + warnings: z.array(z.string()).optional(), +}); + +/** + * Plugin Scaffolding Template + * Template for plugin structure + */ +export const PluginScaffoldingTemplateSchema = z.object({ + /** + * Template identifier + */ + id: z.string(), + + /** + * Template name + */ + name: z.string(), + + /** + * Description + */ + description: z.string(), + + /** + * Plugin type + */ + pluginType: z.string(), + + /** + * File structure + */ + structure: z.array(z.object({ + type: z.enum(['file', 'directory']), + path: z.string(), + template: z.string().optional().describe('Template content with variables'), + optional: z.boolean().default(false), + })), + + /** + * Variables to be filled + */ + variables: z.array(z.object({ + name: z.string(), + description: z.string(), + type: z.enum(['string', 'number', 'boolean', 'array', 'object']), + required: z.boolean().default(true), + default: z.any().optional(), + validation: z.string().optional().describe('Validation regex or rule'), + })), + + /** + * Post-scaffold scripts + */ + scripts: z.array(z.object({ + name: z.string(), + command: z.string(), + description: z.string().optional(), + optional: z.boolean().default(false), + })).optional(), +}); + +/** + * AI Code Review Result + * Result of AI-powered code review + */ +export const AICodeReviewResultSchema = z.object({ + /** + * Overall assessment + */ + assessment: z.enum(['excellent', 'good', 'acceptable', 'needs-improvement', 'poor']), + + /** + * Overall score (0-100) + */ + score: z.number().min(0).max(100), + + /** + * Issues found + */ + issues: z.array(z.object({ + severity: z.enum(['critical', 'error', 'warning', 'info', 'style']), + category: z.enum([ + 'bug', + 'security', + 'performance', + 'maintainability', + 'style', + 'documentation', + 'testing', + 'type-safety', + 'best-practice', + ]), + file: z.string(), + line: z.number().int().optional(), + column: z.number().int().optional(), + message: z.string(), + suggestion: z.string().optional(), + autoFixable: z.boolean().default(false), + autoFix: z.string().optional().describe('Automated fix code'), + })), + + /** + * Positive highlights + */ + highlights: z.array(z.object({ + category: z.string(), + description: z.string(), + file: z.string().optional(), + })).optional(), + + /** + * Quality metrics + */ + metrics: z.object({ + complexity: z.number().optional(), + maintainability: z.number().min(0).max(100).optional(), + testCoverage: z.number().min(0).max(100).optional(), + duplicateCode: z.number().min(0).max(100).optional(), + technicalDebt: z.string().optional().describe('Estimated technical debt'), + }).optional(), + + /** + * Recommendations + */ + recommendations: z.array(z.object({ + priority: z.enum(['high', 'medium', 'low']), + title: z.string(), + description: z.string(), + effort: z.enum(['trivial', 'small', 'medium', 'large']).optional(), + })), + + /** + * Security analysis + */ + security: z.object({ + vulnerabilities: z.array(z.object({ + severity: z.enum(['critical', 'high', 'medium', 'low']), + type: z.string(), + description: z.string(), + remediation: z.string().optional(), + })).optional(), + score: z.number().min(0).max(100).optional(), + }).optional(), +}); + +/** + * Plugin Composition Request + * Request for AI to compose multiple plugins together + */ +export const PluginCompositionRequestSchema = z.object({ + /** + * Desired outcome + */ + goal: z.string().describe('What should the composed plugins achieve'), + + /** + * Available plugins + */ + availablePlugins: z.array(z.object({ + pluginId: z.string(), + version: z.string(), + capabilities: z.array(z.string()).optional(), + description: z.string().optional(), + })), + + /** + * Constraints + */ + constraints: z.object({ + /** + * Maximum plugins to use + */ + maxPlugins: z.number().int().min(1).optional(), + + /** + * Required plugins + */ + requiredPlugins: z.array(z.string()).optional(), + + /** + * Excluded plugins + */ + excludedPlugins: z.array(z.string()).optional(), + + /** + * Performance requirements + */ + performance: z.object({ + maxLatency: z.number().optional().describe('Maximum latency in ms'), + maxMemory: z.number().optional().describe('Maximum memory in bytes'), + }).optional(), + }).optional(), + + /** + * Optimization criteria + */ + optimize: z.enum([ + 'performance', + 'reliability', + 'simplicity', + 'cost', + 'security', + ]).optional(), +}); + +/** + * Plugin Composition Result + * AI-generated plugin composition + */ +export const PluginCompositionResultSchema = z.object({ + /** + * Selected plugins + */ + plugins: z.array(z.object({ + pluginId: z.string(), + version: z.string(), + role: z.string().describe('Role in the composition'), + configuration: z.record(z.string(), z.any()).optional(), + })), + + /** + * Integration code + */ + integration: z.object({ + /** + * Glue code to connect plugins + */ + code: z.string(), + + /** + * Configuration + */ + config: z.record(z.string(), z.any()).optional(), + + /** + * Initialization order + */ + initOrder: z.array(z.string()), + }), + + /** + * Data flow diagram + */ + dataFlow: z.array(z.object({ + from: z.string(), + to: z.string(), + data: z.string().describe('Data type or description'), + })), + + /** + * Expected performance + */ + performance: z.object({ + estimatedLatency: z.number().optional().describe('Estimated latency in ms'), + estimatedMemory: z.number().optional().describe('Estimated memory in bytes'), + }).optional(), + + /** + * Confidence score + */ + confidence: z.number().min(0).max(100), + + /** + * Alternative compositions + */ + alternatives: z.array(z.object({ + description: z.string(), + plugins: z.array(z.string()), + tradeoffs: z.string(), + })).optional(), + + /** + * Warnings and considerations + */ + warnings: z.array(z.string()).optional(), +}); + +/** + * Plugin Recommendation Request + * Request for plugin recommendations + */ +export const PluginRecommendationRequestSchema = z.object({ + /** + * User context + */ + context: z.object({ + /** + * Current plugins installed + */ + installedPlugins: z.array(z.string()).optional(), + + /** + * User's industry + */ + industry: z.string().optional(), + + /** + * Use cases + */ + useCases: z.array(z.string()).optional(), + + /** + * Team size + */ + teamSize: z.number().int().optional(), + + /** + * Budget constraints + */ + budget: z.enum(['free', 'low', 'medium', 'high', 'unlimited']).optional(), + }), + + /** + * Recommendation criteria + */ + criteria: z.object({ + /** + * Prioritize by + */ + prioritize: z.enum([ + 'popularity', + 'rating', + 'compatibility', + 'features', + 'cost', + 'support', + ]).optional(), + + /** + * Only certified plugins + */ + certifiedOnly: z.boolean().default(false), + + /** + * Minimum rating + */ + minRating: z.number().min(0).max(5).optional(), + + /** + * Maximum results + */ + maxResults: z.number().int().min(1).max(50).default(10), + }).optional(), +}); + +/** + * Plugin Recommendation + * AI-generated plugin recommendation + */ +export const PluginRecommendationSchema = z.object({ + /** + * Recommended plugins + */ + recommendations: z.array(z.object({ + pluginId: z.string(), + name: z.string(), + description: z.string(), + score: z.number().min(0).max(100).describe('Relevance score'), + reasons: z.array(z.string()).describe('Why this plugin is recommended'), + benefits: z.array(z.string()), + considerations: z.array(z.string()).optional(), + alternatives: z.array(z.string()).optional(), + estimatedValue: z.string().optional().describe('Expected value/ROI'), + })), + + /** + * Recommended combinations + */ + combinations: z.array(z.object({ + plugins: z.array(z.string()), + description: z.string(), + synergies: z.array(z.string()).describe('How these plugins work well together'), + totalScore: z.number().min(0).max(100), + })).optional(), + + /** + * Learning path + */ + learningPath: z.array(z.object({ + step: z.number().int(), + plugin: z.string(), + reason: z.string(), + resources: z.array(z.string()).optional(), + })).optional(), +}); + +// Export types +export type CodeGenerationRequest = z.infer; +export type GeneratedCode = z.infer; +export type PluginScaffoldingTemplate = z.infer; +export type AICodeReviewResult = z.infer; +export type PluginCompositionRequest = z.infer; +export type PluginCompositionResult = z.infer; +export type PluginRecommendationRequest = z.infer; +export type PluginRecommendation = z.infer; 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/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/index.ts b/packages/spec/src/hub/index.ts index a8708a3e9..cedb8a1e7 100644 --- a/packages/spec/src/hub/index.ts +++ b/packages/spec/src/hub/index.ts @@ -1,6 +1,7 @@ // Export Hub Components export * from './composer.zod'; export * from './marketplace.zod'; +export * from './marketplace-enhanced.zod'; export * from './plugin-registry.zod'; export * from './space.zod'; export * from './tenant.zod'; 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/hub/marketplace-enhanced.zod.ts b/packages/spec/src/hub/marketplace-enhanced.zod.ts new file mode 100644 index 000000000..1760a9a89 --- /dev/null +++ b/packages/spec/src/hub/marketplace-enhanced.zod.ts @@ -0,0 +1,863 @@ +import { z } from 'zod'; +import { PluginSecurityManifestSchema } from '../system/plugin-security-advanced.zod'; +import { PluginVersionMetadataSchema } from '../system/plugin-versioning.zod'; + +/** + * # Enhanced Plugin Registry and Marketplace Protocol + * + * Comprehensive protocol for plugin discovery, distribution, installation, + * and lifecycle management in a centralized marketplace. + * + * Features: + * - Plugin discovery and search + * - Rating and review system + * - Quality scoring and certification + * - 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 + */ +export const PluginCategorySchema = z.enum([ + 'data-integration', // Data connectors and integrations + 'analytics', // Analytics and BI tools + 'ai-ml', // AI and machine learning + 'automation', // Workflow automation + 'communication', // Communication tools + 'crm', // CRM extensions + 'erp', // ERP extensions + 'productivity', // Productivity tools + 'security', // Security tools + 'ui-components', // UI widgets and components + 'utilities', // Utility plugins + 'developer-tools', // Development tools + 'other', // Other categories +]).describe('Plugin category for classification'); + +/** + * Plugin Tag + * Flexible tagging for discovery + */ +export const PluginTagSchema = z.object({ + name: z.string().describe('Tag name'), + category: z.enum(['feature', 'technology', 'industry', 'custom']).optional(), +}); + +/** + * Plugin Rating + * User rating for a plugin + */ +export const PluginRatingSchema = z.object({ + /** + * User who rated + */ + userId: z.string(), + + /** + * Rating value (1-5 stars) + */ + rating: z.number().min(1).max(5), + + /** + * Optional review text + */ + review: z.string().optional(), + + /** + * Rating timestamp + */ + timestamp: z.string().datetime(), + + /** + * Verified purchase + */ + verifiedPurchase: z.boolean().default(false), + + /** + * Helpful votes + */ + helpfulVotes: z.number().int().min(0).default(0), +}); + +/** + * Plugin Quality Metrics (Marketplace) + * Objective quality measurements for marketplace listing + */ +export const MarketplaceQualityMetricsSchema = z.object({ + /** + * Code quality score (0-100) + */ + codeQuality: z.number().min(0).max(100).optional(), + + /** + * Test coverage percentage + */ + testCoverage: z.number().min(0).max(100).optional(), + + /** + * Documentation completeness (0-100) + */ + documentation: z.number().min(0).max(100).optional(), + + /** + * Performance score (0-100) + */ + performance: z.number().min(0).max(100).optional(), + + /** + * Security score (0-100) + */ + security: z.number().min(0).max(100).optional(), + + /** + * Maintainability index + */ + maintainability: z.number().min(0).max(100).optional(), + + /** + * Number of open issues + */ + openIssues: z.number().int().min(0).optional(), + + /** + * Average issue resolution time (hours) + */ + avgIssueResolutionTime: z.number().optional(), + + /** + * Update frequency (days) + */ + updateFrequency: z.number().optional(), + + /** + * Last update date + */ + lastUpdated: z.string().datetime().optional(), +}); + +/** + * Plugin Certification + * Official certification status + */ +export const PluginCertificationSchema = z.object({ + /** + * Certification level + */ + level: z.enum([ + 'verified', // Identity verified + 'tested', // Passed basic tests + 'certified', // Full certification + 'enterprise', // Enterprise-grade certification + 'partner', // Official partner + ]), + + /** + * Certification date + */ + certifiedDate: z.string().datetime(), + + /** + * Expiry date + */ + expiryDate: z.string().datetime().optional(), + + /** + * Certification authority + */ + authority: z.string(), + + /** + * Certificate ID + */ + certificateId: z.string().optional(), + + /** + * Badge URL + */ + badgeUrl: z.string().url().optional(), +}); + +/** + * Plugin License + * Licensing information + */ +export const PluginLicenseSchema = z.object({ + /** + * License type + */ + type: z.enum([ + 'free', // Free to use + 'open-source', // Open source license + 'freemium', // Free with premium features + 'trial', // Trial period + 'subscription', // Subscription-based + 'perpetual', // One-time purchase + 'enterprise', // Enterprise licensing + 'custom', // Custom licensing + ]), + + /** + * SPDX license identifier + */ + spdxId: z.string().optional().describe('SPDX license identifier (e.g., MIT, Apache-2.0)'), + + /** + * License text or URL + */ + licenseText: z.string().optional(), + + /** + * License URL + */ + licenseUrl: z.string().url().optional(), + + /** + * Commercial use allowed + */ + commercialUse: z.boolean().default(true), + + /** + * Attribution required + */ + attributionRequired: z.boolean().default(false), + + /** + * Pricing information + */ + pricing: z.object({ + /** + * Free tier available + */ + freeTier: z.boolean().default(false), + + /** + * Trial period (days) + */ + trialDays: z.number().int().min(0).optional(), + + /** + * Pricing model + */ + model: z.enum(['free', 'per-user', 'per-tenant', 'usage-based', 'flat-rate']).optional(), + + /** + * Price per unit (in cents) + */ + pricePerUnit: z.number().int().min(0).optional(), + + /** + * Billing period + */ + billingPeriod: z.enum(['monthly', 'annually', 'one-time']).optional(), + + /** + * Currency + */ + currency: z.string().default('USD'), + }).optional(), +}); + +/** + * Plugin Marketplace Listing + * Complete marketplace information for a plugin + */ +export const PluginMarketplaceListingSchema = z.object({ + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Display name + */ + name: z.string(), + + /** + * Short description (for listings) + */ + shortDescription: z.string().max(200), + + /** + * Full description (supports markdown) + */ + description: z.string(), + + /** + * Author/publisher information + */ + publisher: z.object({ + id: z.string(), + name: z.string(), + website: z.string().url().optional(), + email: z.string().email().optional(), + verified: z.boolean().default(false), + }), + + /** + * Categories + */ + categories: z.array(PluginCategorySchema), + + /** + * Tags + */ + tags: z.array(PluginTagSchema).optional(), + + /** + * Version information + */ + versions: z.array(PluginVersionMetadataSchema), + + /** + * Latest stable version + */ + latestVersion: z.string(), + + /** + * Icon URL + */ + icon: z.string().url().optional(), + + /** + * Screenshots + */ + screenshots: z.array(z.object({ + url: z.string().url(), + caption: z.string().optional(), + thumbnail: z.string().url().optional(), + })).optional(), + + /** + * Demo/video URL + */ + demoUrl: z.string().url().optional(), + + /** + * Documentation URL + */ + documentationUrl: z.string().url().optional(), + + /** + * Source code URL + */ + sourceUrl: z.string().url().optional(), + + /** + * License + */ + license: PluginLicenseSchema, + + /** + * Ratings + */ + ratings: z.object({ + average: z.number().min(0).max(5), + count: z.number().int().min(0), + distribution: z.object({ + '1': z.number().int().min(0), + '2': z.number().int().min(0), + '3': z.number().int().min(0), + '4': z.number().int().min(0), + '5': z.number().int().min(0), + }), + reviews: z.array(PluginRatingSchema).optional(), + }).optional(), + + /** + * Quality metrics + */ + quality: MarketplaceQualityMetricsSchema.optional(), + + /** + * Certification + */ + certification: PluginCertificationSchema.optional(), + + /** + * Security information + */ + security: PluginSecurityManifestSchema.optional(), + + /** + * Statistics + */ + statistics: z.object({ + downloads: z.number().int().min(0), + activeInstallations: z.number().int().min(0), + views: z.number().int().min(0).optional(), + favorites: z.number().int().min(0).optional(), + }), + + /** + * Support information + */ + support: z.object({ + email: z.string().email().optional(), + url: z.string().url().optional(), + forum: z.string().url().optional(), + chat: z.string().url().optional(), + phone: z.string().optional(), + responseTime: z.string().optional().describe('Expected response time'), + sla: z.string().url().optional().describe('SLA document URL'), + }).optional(), + + /** + * Marketplace metadata + */ + marketplace: z.object({ + /** + * Featured plugin + */ + featured: z.boolean().default(false), + + /** + * Editor's choice + */ + editorsChoice: z.boolean().default(false), + + /** + * New release + */ + newRelease: z.boolean().default(false), + + /** + * Trending + */ + trending: z.boolean().default(false), + + /** + * Rank in category + */ + categoryRank: z.number().int().min(1).optional(), + + /** + * Overall rank + */ + overallRank: z.number().int().min(1).optional(), + }).optional(), + + /** + * First published date + */ + publishedDate: z.string().datetime(), + + /** + * Last updated date + */ + lastUpdated: z.string().datetime(), +}); + +/** + * Plugin Search Query + * Search and filter criteria + */ +export const PluginSearchQuerySchema = z.object({ + /** + * Search text + */ + query: z.string().optional(), + + /** + * Filter by category + */ + category: PluginCategorySchema.optional(), + + /** + * Filter by tags + */ + tags: z.array(z.string()).optional(), + + /** + * Filter by license type + */ + licenseType: z.array(z.string()).optional(), + + /** + * Minimum rating + */ + minRating: z.number().min(0).max(5).optional(), + + /** + * Minimum quality score + */ + minQualityScore: z.number().min(0).max(100).optional(), + + /** + * Certification required + */ + certifiedOnly: z.boolean().optional(), + + /** + * Free plugins only + */ + freeOnly: z.boolean().optional(), + + /** + * Sort by + */ + sortBy: z.enum([ + 'relevance', + 'popularity', + 'rating', + 'downloads', + 'newest', + 'updated', + 'name', + ]).optional(), + + /** + * Sort order + */ + sortOrder: z.enum(['asc', 'desc']).default('desc'), + + /** + * Pagination + */ + page: z.number().int().min(1).default(1), + pageSize: z.number().int().min(1).max(100).default(20), +}); + +/** + * Plugin Installation Request + * Request to install a plugin + */ +export const PluginInstallationRequestSchema = z.object({ + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Version to install + */ + version: z.string().optional().describe('If not specified, latest stable version'), + + /** + * Installation configuration + */ + config: z.record(z.string(), z.any()).optional(), + + /** + * Accept license + */ + acceptLicense: z.boolean().default(false), + + /** + * Grant permissions + */ + grantPermissions: z.array(z.string()).optional(), + + /** + * Enable automatically + */ + autoEnable: z.boolean().default(true), + + /** + * Installation scope + */ + scope: z.enum(['global', 'tenant', 'user']).default('global'), + + /** + * Tenant ID (if scope is tenant) + */ + tenantId: z.string().optional(), +}); + +/** + * Plugin Installation Status + * Status of plugin installation + */ +export const PluginInstallationStatusSchema = z.object({ + /** + * Installation ID + */ + installationId: z.string(), + + /** + * Plugin ID + */ + pluginId: z.string(), + + /** + * Version installed + */ + version: z.string(), + + /** + * Installation status + */ + status: z.enum([ + 'pending', + 'downloading', + 'verifying', + 'installing', + 'configuring', + 'completed', + 'failed', + 'rollback', + ]), + + /** + * Progress percentage + */ + progress: z.number().min(0).max(100).optional(), + + /** + * Status message + */ + message: z.string().optional(), + + /** + * Error details (if failed) + */ + error: z.object({ + code: z.string(), + message: z.string(), + details: z.any().optional(), + }).optional(), + + /** + * Started timestamp + */ + startedAt: z.string().datetime(), + + /** + * Completed timestamp + */ + completedAt: z.string().datetime().optional(), +}); + +/** + * Plugin Revenue Sharing + * Revenue sharing configuration for paid plugins + */ +export const PluginRevenueSharingSchema = z.object({ + /** + * Plugin ID + */ + pluginId: z.string(), + + /** + * Developer share percentage + */ + developerShare: z.number().min(0).max(100).describe('Percentage going to developer'), + + /** + * Platform share percentage + */ + platformShare: z.number().min(0).max(100).describe('Percentage going to platform'), + + /** + * Payment schedule + */ + paymentSchedule: z.enum(['monthly', 'quarterly', 'annually']).default('monthly'), + + /** + * Minimum payout threshold (in cents) + */ + minimumPayout: z.number().int().min(0).default(10000).describe('Minimum $100 default'), + + /** + * Payment method + */ + paymentMethod: z.object({ + type: z.enum(['bank-transfer', 'paypal', 'stripe', 'other']), + details: z.record(z.string(), z.any()).optional(), + }), + + /** + * Tax information + */ + taxInfo: z.object({ + taxId: z.string().optional(), + country: z.string(), + taxExempt: z.boolean().default(false), + }).optional(), +}); + +// 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; +export type MarketplaceQualityMetrics = z.infer; +export type PluginCertification = z.infer; +export type PluginLicense = z.infer; +export type PluginMarketplaceListing = z.infer; +export type PluginSearchQuery = z.infer; +export type PluginInstallationRequest = z.infer; +export type PluginInstallationStatus = z.infer; +export type PluginRevenueSharing = z.infer; diff --git a/packages/spec/src/system/index.ts b/packages/spec/src/system/index.ts index bcb30b99d..e0d47d123 100644 --- a/packages/spec/src/system/index.ts +++ b/packages/spec/src/system/index.ts @@ -28,6 +28,9 @@ export * from './plugin-capability.zod'; export * from './plugin-loading.zod'; export * from './plugin-validator.zod'; export * from './plugin-lifecycle-events.zod'; +export * from './plugin-lifecycle-advanced.zod'; +export * from './plugin-versioning.zod'; +export * from './plugin-security-advanced.zod'; export * from './startup-orchestrator.zod'; export * from './service-registry.zod'; export * from './context.zod'; diff --git a/packages/spec/src/system/plugin-lifecycle-advanced.test.ts b/packages/spec/src/system/plugin-lifecycle-advanced.test.ts new file mode 100644 index 000000000..182278cd9 --- /dev/null +++ b/packages/spec/src/system/plugin-lifecycle-advanced.test.ts @@ -0,0 +1,394 @@ +import { describe, expect, it } from 'vitest'; +import { + PluginHealthStatusSchema, + PluginHealthCheckSchema, + PluginHealthReportSchema, + DistributedStateConfigSchema, + HotReloadConfigSchema, + GracefulDegradationSchema, + PluginUpdateStrategySchema, + PluginStateSnapshotSchema, + AdvancedPluginLifecycleConfigSchema, +} from './plugin-lifecycle-advanced.zod'; + +describe('Plugin Lifecycle Advanced Schemas', () => { + describe('PluginHealthStatusSchema', () => { + it('should validate valid health statuses', () => { + expect(() => PluginHealthStatusSchema.parse('healthy')).not.toThrow(); + expect(() => PluginHealthStatusSchema.parse('degraded')).not.toThrow(); + expect(() => PluginHealthStatusSchema.parse('unhealthy')).not.toThrow(); + expect(() => PluginHealthStatusSchema.parse('failed')).not.toThrow(); + expect(() => PluginHealthStatusSchema.parse('recovering')).not.toThrow(); + expect(() => PluginHealthStatusSchema.parse('unknown')).not.toThrow(); + }); + + it('should reject invalid health statuses', () => { + expect(() => PluginHealthStatusSchema.parse('invalid')).toThrow(); + expect(() => PluginHealthStatusSchema.parse('')).toThrow(); + }); + }); + + describe('PluginHealthCheckSchema', () => { + it('should validate health check with defaults', () => { + const healthCheck = PluginHealthCheckSchema.parse({}); + expect(healthCheck.interval).toBe(30000); + expect(healthCheck.timeout).toBe(5000); + expect(healthCheck.failureThreshold).toBe(3); + expect(healthCheck.successThreshold).toBe(1); + expect(healthCheck.autoRestart).toBe(false); + expect(healthCheck.maxRestartAttempts).toBe(3); + expect(healthCheck.restartBackoff).toBe('exponential'); + }); + + it('should validate custom health check configuration', () => { + const config = { + interval: 60000, + timeout: 10000, + failureThreshold: 5, + successThreshold: 2, + autoRestart: true, + maxRestartAttempts: 5, + restartBackoff: 'linear' as const, + checkMethod: 'healthCheck', + }; + const healthCheck = PluginHealthCheckSchema.parse(config); + expect(healthCheck).toEqual(config); + }); + + it('should enforce minimum interval', () => { + expect(() => PluginHealthCheckSchema.parse({ interval: 500 })).toThrow(); + }); + + it('should enforce minimum timeout', () => { + expect(() => PluginHealthCheckSchema.parse({ timeout: 50 })).toThrow(); + }); + }); + + describe('PluginHealthReportSchema', () => { + it('should validate complete health report', () => { + const report = { + status: 'healthy' as const, + timestamp: new Date().toISOString(), + message: 'Plugin is operating normally', + metrics: { + uptime: 3600000, + memoryUsage: 52428800, + cpuUsage: 15.5, + activeConnections: 10, + errorRate: 0.1, + responseTime: 150, + }, + checks: [ + { + name: 'database', + status: 'passed' as const, + message: 'Database connection is healthy', + }, + { + name: 'cache', + status: 'passed' as const, + }, + ], + dependencies: [ + { + pluginId: 'com.objectstack.driver.postgres', + status: 'healthy' as const, + }, + ], + }; + const result = PluginHealthReportSchema.parse(report); + expect(result.status).toBe('healthy'); + expect(result.metrics?.uptime).toBe(3600000); + expect(result.checks).toHaveLength(2); + }); + + it('should validate minimal health report', () => { + const report = { + status: 'healthy' as const, + timestamp: new Date().toISOString(), + }; + const result = PluginHealthReportSchema.parse(report); + expect(result.status).toBe('healthy'); + }); + }); + + describe('HotReloadConfigSchema', () => { + it('should validate hot reload with defaults', () => { + const config = HotReloadConfigSchema.parse({}); + expect(config.enabled).toBe(false); + expect(config.debounceDelay).toBe(1000); + expect(config.preserveState).toBe(true); + expect(config.stateStrategy).toBe('memory'); + expect(config.shutdownTimeout).toBe(30000); + }); + + it('should validate custom hot reload configuration', () => { + const config = { + enabled: true, + watchPatterns: ['src/**/*.ts', 'config/**/*.json'], + debounceDelay: 2000, + preserveState: false, + stateStrategy: 'disk' as const, + shutdownTimeout: 60000, + beforeReload: ['beforeReloadHook'], + afterReload: ['afterReloadHook'], + }; + 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', () => { + it('should validate graceful degradation with defaults', () => { + const config = GracefulDegradationSchema.parse({}); + expect(config.enabled).toBe(true); + expect(config.fallbackMode).toBe('minimal'); + }); + + it('should validate complete degradation configuration', () => { + const config = { + enabled: true, + fallbackMode: 'readonly' as const, + criticalDependencies: ['com.objectstack.driver.postgres'], + optionalDependencies: ['com.acme.analytics'], + degradedFeatures: [ + { + feature: 'advanced-search', + enabled: false, + reason: 'Search engine unavailable', + }, + ], + autoRecovery: { + enabled: true, + retryInterval: 120000, + maxAttempts: 10, + }, + }; + const result = GracefulDegradationSchema.parse(config); + expect(result.fallbackMode).toBe('readonly'); + expect(result.criticalDependencies).toHaveLength(1); + }); + }); + + describe('PluginUpdateStrategySchema', () => { + it('should validate update strategy with defaults', () => { + const strategy = PluginUpdateStrategySchema.parse({}); + expect(strategy.mode).toBe('manual'); + }); + + it('should validate automatic update strategy', () => { + const strategy = { + mode: 'automatic' as const, + autoUpdateConstraints: { + major: false, + minor: true, + patch: true, + }, + rollback: { + enabled: true, + automatic: true, + keepVersions: 5, + timeout: 60000, + }, + validation: { + checkCompatibility: true, + runTests: true, + testSuite: 'integration', + }, + }; + const result = PluginUpdateStrategySchema.parse(strategy); + expect(result.mode).toBe('automatic'); + expect(result.autoUpdateConstraints?.patch).toBe(true); + }); + + it('should validate scheduled update strategy', () => { + const strategy = { + mode: 'scheduled' as const, + schedule: { + cron: '0 2 * * 0', + timezone: 'America/New_York', + maintenanceWindow: 120, + }, + }; + const result = PluginUpdateStrategySchema.parse(strategy); + expect(result.mode).toBe('scheduled'); + expect(result.schedule?.timezone).toBe('America/New_York'); + }); + }); + + describe('PluginStateSnapshotSchema', () => { + it('should validate state snapshot', () => { + const snapshot = { + pluginId: 'com.acme.plugin', + version: '1.2.3', + timestamp: new Date().toISOString(), + state: { + counter: 42, + cache: { key1: 'value1' }, + settings: { theme: 'dark' }, + }, + metadata: { + checksum: 'abc123def456', + compressed: true, + encryption: 'AES-256', + }, + }; + const result = PluginStateSnapshotSchema.parse(snapshot); + expect(result.pluginId).toBe('com.acme.plugin'); + expect(result.state.counter).toBe(42); + expect(result.metadata?.compressed).toBe(true); + }); + }); + + describe('AdvancedPluginLifecycleConfigSchema', () => { + it('should validate empty config', () => { + const config = AdvancedPluginLifecycleConfigSchema.parse({}); + expect(config).toBeDefined(); + }); + + it('should validate complete lifecycle configuration', () => { + const config = { + health: { + interval: 60000, + timeout: 10000, + failureThreshold: 3, + autoRestart: true, + }, + hotReload: { + enabled: true, + watchPatterns: ['src/**/*.ts'], + preserveState: true, + }, + degradation: { + enabled: true, + fallbackMode: 'minimal' as const, + }, + updates: { + mode: 'automatic' as const, + autoUpdateConstraints: { + patch: true, + }, + }, + resources: { + maxMemory: 536870912, + maxCpu: 80, + maxConnections: 100, + timeout: 30000, + }, + observability: { + enableMetrics: true, + enableTracing: true, + enableProfiling: false, + metricsInterval: 60000, + }, + }; + const result = AdvancedPluginLifecycleConfigSchema.parse(config); + expect(result.health?.interval).toBe(60000); + expect(result.hotReload?.enabled).toBe(true); + expect(result.resources?.maxMemory).toBe(536870912); + expect(result.observability?.enableMetrics).toBe(true); + }); + }); + + describe('Integration scenarios', () => { + it('should support plugin with health monitoring and hot reload', () => { + const config = AdvancedPluginLifecycleConfigSchema.parse({ + health: { + interval: 30000, + autoRestart: true, + maxRestartAttempts: 3, + }, + hotReload: { + enabled: true, + preserveState: true, + stateStrategy: 'memory' as const, + }, + }); + expect(config.health?.autoRestart).toBe(true); + expect(config.hotReload?.enabled).toBe(true); + }); + + it('should support plugin with graceful degradation', () => { + const config = AdvancedPluginLifecycleConfigSchema.parse({ + degradation: { + enabled: true, + fallbackMode: 'readonly' as const, + criticalDependencies: ['database'], + optionalDependencies: ['cache', 'analytics'], + autoRecovery: { + enabled: true, + retryInterval: 60000, + maxAttempts: 5, + }, + }, + }); + expect(config.degradation?.fallbackMode).toBe('readonly'); + expect(config.degradation?.criticalDependencies).toHaveLength(1); + }); + }); +}); diff --git a/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts b/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts new file mode 100644 index 000000000..561088094 --- /dev/null +++ b/packages/spec/src/system/plugin-lifecycle-advanced.zod.ts @@ -0,0 +1,480 @@ +import { z } from 'zod'; + +/** + * # Advanced Plugin Lifecycle Protocol + * + * Defines advanced lifecycle management capabilities including: + * - Hot reload and live updates + * - Graceful degradation and fallback mechanisms + * - Health monitoring and auto-recovery + * - State preservation during updates + * + * This protocol extends the basic plugin lifecycle with enterprise-grade + * features for production environments. + */ + +/** + * Plugin Health Status + * Represents the current operational state of a plugin + */ +export const PluginHealthStatusSchema = z.enum([ + 'healthy', // Plugin is operating normally + 'degraded', // Plugin is operational but with reduced functionality + 'unhealthy', // Plugin has critical issues but still running + 'failed', // Plugin has failed and is not operational + 'recovering', // Plugin is in recovery process + 'unknown', // Health status cannot be determined +]).describe('Current health status of the plugin'); + +/** + * Plugin Health Check Configuration + * Defines how to check plugin health + */ +export const PluginHealthCheckSchema = z.object({ + /** + * Health check interval in milliseconds + */ + interval: z.number().int().min(1000).default(30000) + .describe('How often to perform health checks (default: 30s)'), + + /** + * Timeout for health check in milliseconds + */ + timeout: z.number().int().min(100).default(5000) + .describe('Maximum time to wait for health check response'), + + /** + * Number of consecutive failures before marking as unhealthy + */ + failureThreshold: z.number().int().min(1).default(3) + .describe('Consecutive failures needed to mark unhealthy'), + + /** + * Number of consecutive successes to recover from unhealthy state + */ + successThreshold: z.number().int().min(1).default(1) + .describe('Consecutive successes needed to mark healthy'), + + /** + * Custom health check function name or endpoint + */ + checkMethod: z.string().optional() + .describe('Method name to call for health check'), + + /** + * Enable automatic restart on failure + */ + autoRestart: z.boolean().default(false) + .describe('Automatically restart plugin on health check failure'), + + /** + * Maximum number of restart attempts + */ + maxRestartAttempts: z.number().int().min(0).default(3) + .describe('Maximum restart attempts before giving up'), + + /** + * Backoff strategy for restarts + */ + restartBackoff: z.enum(['fixed', 'linear', 'exponential']).default('exponential') + .describe('Backoff strategy for restart delays'), +}); + +/** + * Plugin Health Report + * Detailed health information from a plugin + */ +export const PluginHealthReportSchema = z.object({ + /** + * Overall health status + */ + status: PluginHealthStatusSchema, + + /** + * Timestamp of the health check + */ + timestamp: z.string().datetime(), + + /** + * Human-readable message about health status + */ + message: z.string().optional(), + + /** + * Detailed metrics + */ + metrics: z.object({ + uptime: z.number().describe('Plugin uptime in milliseconds'), + memoryUsage: z.number().optional().describe('Memory usage in bytes'), + cpuUsage: z.number().optional().describe('CPU usage percentage'), + activeConnections: z.number().optional().describe('Number of active connections'), + errorRate: z.number().optional().describe('Error rate (errors per minute)'), + responseTime: z.number().optional().describe('Average response time in ms'), + }).partial().optional(), + + /** + * List of checks performed + */ + checks: z.array(z.object({ + name: z.string().describe('Check name'), + status: z.enum(['passed', 'failed', 'warning']), + message: z.string().optional(), + data: z.record(z.string(), z.any()).optional(), + })).optional(), + + /** + * Dependencies health + */ + dependencies: z.array(z.object({ + pluginId: z.string(), + status: PluginHealthStatusSchema, + message: z.string().optional(), + })).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 + */ +export const HotReloadConfigSchema = z.object({ + /** + * Enable hot reload capability + */ + enabled: z.boolean().default(false), + + /** + * Watch file patterns for auto-reload + */ + watchPatterns: z.array(z.string()).optional() + .describe('Glob patterns to watch for changes'), + + /** + * Debounce delay before reloading (milliseconds) + */ + debounceDelay: z.number().int().min(0).default(1000) + .describe('Wait time after change detection before reload'), + + /** + * Preserve plugin state during reload + */ + preserveState: z.boolean().default(true) + .describe('Keep plugin state across reloads'), + + /** + * State serialization strategy + */ + 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 + */ + shutdownTimeout: z.number().int().min(0).default(30000) + .describe('Maximum time to wait for graceful shutdown'), + + /** + * Pre-reload hooks + */ + beforeReload: z.array(z.string()).optional() + .describe('Hook names to call before reload'), + + /** + * Post-reload hooks + */ + afterReload: z.array(z.string()).optional() + .describe('Hook names to call after reload'), +}); + +/** + * Graceful Degradation Configuration + * Defines how plugin degrades when dependencies fail + */ +export const GracefulDegradationSchema = z.object({ + /** + * Enable graceful degradation + */ + enabled: z.boolean().default(true), + + /** + * Fallback mode when dependencies fail + */ + fallbackMode: z.enum([ + 'minimal', // Provide minimal functionality + 'cached', // Use cached data + 'readonly', // Allow read-only operations + 'offline', // Offline mode with local data + 'disabled', // Disable plugin functionality + ]).default('minimal'), + + /** + * Critical dependencies that must be available + */ + criticalDependencies: z.array(z.string()).optional() + .describe('Plugin IDs that are required for operation'), + + /** + * Optional dependencies that can fail + */ + optionalDependencies: z.array(z.string()).optional() + .describe('Plugin IDs that are nice to have but not required'), + + /** + * Feature flags for degraded mode + */ + degradedFeatures: z.array(z.object({ + feature: z.string().describe('Feature name'), + enabled: z.boolean().describe('Whether feature is available in degraded mode'), + reason: z.string().optional(), + })).optional(), + + /** + * Automatic recovery attempts + */ + autoRecovery: z.object({ + enabled: z.boolean().default(true), + retryInterval: z.number().int().min(1000).default(60000) + .describe('Interval between recovery attempts (ms)'), + maxAttempts: z.number().int().min(0).default(5) + .describe('Maximum recovery attempts before giving up'), + }).optional(), +}); + +/** + * Plugin Update Strategy + * Defines how plugin handles version updates + */ +export const PluginUpdateStrategySchema = z.object({ + /** + * Update mode + */ + mode: z.enum([ + 'manual', // Manual updates only + 'automatic', // Automatic updates + 'scheduled', // Scheduled update windows + 'rolling', // Rolling updates with zero downtime + ]).default('manual'), + + /** + * Version constraints for automatic updates + */ + autoUpdateConstraints: z.object({ + major: z.boolean().default(false).describe('Allow major version updates'), + minor: z.boolean().default(true).describe('Allow minor version updates'), + patch: z.boolean().default(true).describe('Allow patch version updates'), + }).optional(), + + /** + * Update schedule (for scheduled mode) + */ + schedule: z.object({ + /** + * Cron expression for update window + */ + cron: z.string().optional(), + + /** + * Timezone for schedule + */ + timezone: z.string().default('UTC'), + + /** + * Maintenance window duration in minutes + */ + maintenanceWindow: z.number().int().min(1).default(60), + }).optional(), + + /** + * Rollback configuration + */ + rollback: z.object({ + enabled: z.boolean().default(true), + + /** + * Automatic rollback on failure + */ + automatic: z.boolean().default(true), + + /** + * Keep N previous versions for rollback + */ + keepVersions: z.number().int().min(1).default(3), + + /** + * Rollback timeout in milliseconds + */ + timeout: z.number().int().min(1000).default(30000), + }).optional(), + + /** + * Pre-update validation + */ + validation: z.object({ + /** + * Run compatibility checks before update + */ + checkCompatibility: z.boolean().default(true), + + /** + * Run tests before applying update + */ + runTests: z.boolean().default(false), + + /** + * Test suite to run + */ + testSuite: z.string().optional(), + }).optional(), +}); + +/** + * Plugin State Snapshot + * Captures plugin state for preservation during updates/reloads + */ +export const PluginStateSnapshotSchema = z.object({ + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Version at time of snapshot + */ + version: z.string(), + + /** + * Snapshot timestamp + */ + timestamp: z.string().datetime(), + + /** + * Serialized state data + */ + state: z.record(z.string(), z.any()), + + /** + * State metadata + */ + metadata: z.object({ + checksum: z.string().optional().describe('State checksum for verification'), + compressed: z.boolean().default(false), + encryption: z.string().optional().describe('Encryption algorithm if encrypted'), + }).optional(), +}); + +/** + * Advanced Plugin Lifecycle Configuration + * Complete configuration for advanced lifecycle management + */ +export const AdvancedPluginLifecycleConfigSchema = z.object({ + /** + * Health monitoring configuration + */ + health: PluginHealthCheckSchema.optional(), + + /** + * Hot reload configuration + */ + hotReload: HotReloadConfigSchema.optional(), + + /** + * Graceful degradation configuration + */ + degradation: GracefulDegradationSchema.optional(), + + /** + * Update strategy + */ + updates: PluginUpdateStrategySchema.optional(), + + /** + * Resource limits + */ + resources: z.object({ + maxMemory: z.number().int().optional().describe('Maximum memory in bytes'), + maxCpu: z.number().min(0).max(100).optional().describe('Maximum CPU percentage'), + maxConnections: z.number().int().optional().describe('Maximum concurrent connections'), + timeout: z.number().int().optional().describe('Operation timeout in milliseconds'), + }).optional(), + + /** + * Monitoring and observability + */ + observability: z.object({ + enableMetrics: z.boolean().default(true), + enableTracing: z.boolean().default(true), + enableProfiling: z.boolean().default(false), + metricsInterval: z.number().int().min(1000).default(60000) + .describe('Metrics collection interval in ms'), + }).optional(), +}); + +// Export types +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; +export type PluginStateSnapshot = z.infer; +export type AdvancedPluginLifecycleConfig = z.infer; 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); + }); + }); +}); diff --git a/packages/spec/src/system/plugin-security-advanced.zod.ts b/packages/spec/src/system/plugin-security-advanced.zod.ts new file mode 100644 index 000000000..2b80b5269 --- /dev/null +++ b/packages/spec/src/system/plugin-security-advanced.zod.ts @@ -0,0 +1,686 @@ +import { z } from 'zod'; + +/** + * # Plugin Security and Sandboxing Protocol + * + * Defines comprehensive security mechanisms for plugin isolation, permission + * management, and threat protection in the ObjectStack ecosystem. + * + * Features: + * - Fine-grained permission system + * - Resource access control + * - Sandboxing and isolation + * - Security scanning and verification + * - Runtime security monitoring + */ + +/** + * Permission Scope + * Defines the scope of a permission + */ +export const PermissionScopeSchema = z.enum([ + 'global', // Applies to entire system + 'tenant', // Applies to specific tenant + 'user', // Applies to specific user + 'resource', // Applies to specific resource + 'plugin', // Applies within plugin boundaries +]).describe('Scope of permission application'); + +/** + * Permission Action + * Standard CRUD + extended actions + */ +export const PermissionActionSchema = z.enum([ + 'create', // Create new resources + 'read', // Read existing resources + 'update', // Update existing resources + 'delete', // Delete resources + 'execute', // Execute operations/functions + 'manage', // Full management rights + 'configure', // Configuration changes + 'share', // Share with others + 'export', // Export data + 'import', // Import data + 'admin', // Administrative access +]).describe('Type of action being permitted'); + +/** + * Resource Type + * Types of resources that can be accessed + */ +export const ResourceTypeSchema = z.enum([ + 'data.object', // ObjectQL objects + 'data.record', // Individual records + 'data.field', // Specific fields + 'ui.view', // UI views + 'ui.dashboard', // Dashboards + 'ui.report', // Reports + 'system.config', // System configuration + 'system.plugin', // Other plugins + 'system.api', // API endpoints + 'system.service', // System services + 'storage.file', // File storage + 'storage.database', // Database access + 'network.http', // HTTP requests + 'network.websocket', // WebSocket connections + 'process.spawn', // Process spawning + 'process.env', // Environment variables +]).describe('Type of resource being accessed'); + +/** + * Permission Definition + * Defines a single permission requirement + */ +export const PermissionSchema = z.object({ + /** + * Permission identifier + */ + id: z.string().describe('Unique permission identifier'), + + /** + * Resource type + */ + resource: ResourceTypeSchema, + + /** + * Allowed actions + */ + actions: z.array(PermissionActionSchema), + + /** + * Permission scope + */ + scope: PermissionScopeSchema.default('plugin'), + + /** + * Resource filter + */ + filter: z.object({ + /** + * Specific resource IDs + */ + resourceIds: z.array(z.string()).optional(), + + /** + * Filter condition + */ + condition: z.string().optional().describe('Filter expression (e.g., owner = currentUser)'), + + /** + * Field-level access + */ + fields: z.array(z.string()).optional().describe('Allowed fields for data resources'), + }).optional(), + + /** + * Human-readable description + */ + description: z.string(), + + /** + * Whether this permission is required or optional + */ + required: z.boolean().default(true), + + /** + * Justification for permission + */ + justification: z.string().optional().describe('Why this permission is needed'), +}); + +/** + * Permission Set + * Collection of permissions for a plugin + */ +export const PermissionSetSchema = z.object({ + /** + * All permissions required by plugin + */ + permissions: z.array(PermissionSchema), + + /** + * Permission groups for easier management + */ + groups: z.array(z.object({ + name: z.string().describe('Group name'), + description: z.string(), + permissions: z.array(z.string()).describe('Permission IDs in this group'), + })).optional(), + + /** + * Default grant strategy + */ + defaultGrant: z.enum([ + 'prompt', // Always prompt user + 'allow', // Allow by default + 'deny', // Deny by default + 'inherit', // Inherit from parent + ]).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 + */ +export const SandboxConfigSchema = z.object({ + /** + * Enable sandboxing + */ + enabled: z.boolean().default(true), + + /** + * Sandboxing level + */ + level: z.enum([ + 'none', // No sandboxing + 'minimal', // Basic isolation + 'standard', // Standard sandboxing + 'strict', // Strict isolation + 'paranoid', // Maximum isolation + ]).default('standard'), + + /** + * Runtime environment configuration + */ + runtime: RuntimeConfigSchema.optional() + .describe('Execution environment and isolation settings'), + + /** + * File system access + */ + filesystem: z.object({ + mode: z.enum(['none', 'readonly', 'restricted', 'full']).default('restricted'), + allowedPaths: z.array(z.string()).optional().describe('Whitelisted paths'), + deniedPaths: z.array(z.string()).optional().describe('Blacklisted paths'), + maxFileSize: z.number().int().optional().describe('Maximum file size in bytes'), + }).optional(), + + /** + * Network access + */ + network: z.object({ + mode: z.enum(['none', 'local', 'restricted', 'full']).default('restricted'), + allowedHosts: z.array(z.string()).optional().describe('Whitelisted hosts'), + deniedHosts: z.array(z.string()).optional().describe('Blacklisted hosts'), + allowedPorts: z.array(z.number()).optional().describe('Allowed port numbers'), + maxConnections: z.number().int().optional(), + }).optional(), + + /** + * Process execution + */ + process: z.object({ + allowSpawn: z.boolean().default(false).describe('Allow spawning child processes'), + allowedCommands: z.array(z.string()).optional().describe('Whitelisted commands'), + timeout: z.number().int().optional().describe('Process timeout in ms'), + }).optional(), + + /** + * Memory limits + */ + memory: z.object({ + maxHeap: z.number().int().optional().describe('Maximum heap size in bytes'), + maxStack: z.number().int().optional().describe('Maximum stack size in bytes'), + }).optional(), + + /** + * CPU limits + */ + cpu: z.object({ + maxCpuPercent: z.number().min(0).max(100).optional(), + maxThreads: z.number().int().optional(), + }).optional(), + + /** + * Environment variables + */ + environment: z.object({ + mode: z.enum(['none', 'readonly', 'restricted', 'full']).default('readonly'), + allowedVars: z.array(z.string()).optional(), + deniedVars: z.array(z.string()).optional(), + }).optional(), +}); + +/** + * Security Vulnerability + * Represents a known security vulnerability + */ +export const SecurityVulnerabilitySchema = z.object({ + /** + * CVE identifier + */ + cve: z.string().optional(), + + /** + * Vulnerability identifier + */ + id: z.string(), + + /** + * Severity level + */ + severity: z.enum(['critical', 'high', 'medium', 'low', 'info']), + + /** + * Title + */ + title: z.string(), + + /** + * Description + */ + description: z.string(), + + /** + * Affected versions + */ + affectedVersions: z.array(z.string()), + + /** + * Fixed in versions + */ + fixedIn: z.array(z.string()).optional(), + + /** + * CVSS score + */ + cvssScore: z.number().min(0).max(10).optional(), + + /** + * Exploit availability + */ + exploitAvailable: z.boolean().default(false), + + /** + * Patch available + */ + patchAvailable: z.boolean().default(false), + + /** + * Workaround + */ + workaround: z.string().optional(), + + /** + * References + */ + references: z.array(z.string()).optional(), + + /** + * Discovered date + */ + discoveredDate: z.string().datetime().optional(), + + /** + * Published date + */ + publishedDate: z.string().datetime().optional(), +}); + +/** + * Security Scan Result + * Result of security scanning + */ +export const SecurityScanResultSchema = z.object({ + /** + * Scan timestamp + */ + timestamp: z.string().datetime(), + + /** + * Scanner information + */ + scanner: z.object({ + name: z.string(), + version: z.string(), + }), + + /** + * Overall status + */ + status: z.enum(['passed', 'failed', 'warning']), + + /** + * Vulnerabilities found + */ + vulnerabilities: z.array(SecurityVulnerabilitySchema).optional(), + + /** + * Code quality issues + */ + codeIssues: z.array(z.object({ + severity: z.enum(['error', 'warning', 'info']), + type: z.string().describe('Issue type (e.g., sql-injection, xss)'), + file: z.string(), + line: z.number().int().optional(), + message: z.string(), + suggestion: z.string().optional(), + })).optional(), + + /** + * Dependency vulnerabilities + */ + dependencyVulnerabilities: z.array(z.object({ + package: z.string(), + version: z.string(), + vulnerability: SecurityVulnerabilitySchema, + })).optional(), + + /** + * License compliance + */ + licenseCompliance: z.object({ + status: z.enum(['compliant', 'non-compliant', 'unknown']), + issues: z.array(z.object({ + package: z.string(), + license: z.string(), + reason: z.string(), + })).optional(), + }).optional(), + + /** + * Summary statistics + */ + summary: z.object({ + totalVulnerabilities: z.number().int(), + criticalCount: z.number().int(), + highCount: z.number().int(), + mediumCount: z.number().int(), + lowCount: z.number().int(), + infoCount: z.number().int(), + }), +}); + +/** + * Security Policy + * Defines security policies for plugin + */ +export const SecurityPolicySchema = z.object({ + /** + * Content Security Policy + */ + csp: z.object({ + directives: z.record(z.string(), z.array(z.string())).optional(), + reportOnly: z.boolean().default(false), + }).optional(), + + /** + * CORS policy + */ + cors: z.object({ + allowedOrigins: z.array(z.string()), + allowedMethods: z.array(z.string()), + allowedHeaders: z.array(z.string()), + allowCredentials: z.boolean().default(false), + maxAge: z.number().int().optional(), + }).optional(), + + /** + * Rate limiting + */ + rateLimit: z.object({ + enabled: z.boolean().default(true), + maxRequests: z.number().int(), + windowMs: z.number().int().describe('Time window in milliseconds'), + strategy: z.enum(['fixed', 'sliding', 'token-bucket']).default('sliding'), + }).optional(), + + /** + * Authentication requirements + */ + authentication: z.object({ + required: z.boolean().default(true), + methods: z.array(z.enum(['jwt', 'oauth2', 'api-key', 'session', 'certificate'])), + tokenExpiration: z.number().int().optional().describe('Token expiration in seconds'), + }).optional(), + + /** + * Encryption requirements + */ + encryption: z.object({ + dataAtRest: z.boolean().default(false).describe('Encrypt data at rest'), + dataInTransit: z.boolean().default(true).describe('Enforce HTTPS/TLS'), + algorithm: z.string().optional().describe('Encryption algorithm'), + minKeyLength: z.number().int().optional().describe('Minimum key length in bits'), + }).optional(), + + /** + * Audit logging + */ + auditLog: z.object({ + enabled: z.boolean().default(true), + events: z.array(z.string()).optional().describe('Events to log'), + retention: z.number().int().optional().describe('Log retention in days'), + }).optional(), +}); + +/** + * Plugin Trust Level + * Indicates trust level of plugin + */ +export const PluginTrustLevelSchema = z.enum([ + 'verified', // Official/verified plugin + 'trusted', // Trusted third-party + 'community', // Community plugin + 'untrusted', // Unverified plugin + 'blocked', // Blocked/malicious +]).describe('Trust level of the plugin'); + +/** + * Plugin Security Manifest + * Complete security information for plugin + */ +export const PluginSecurityManifestSchema = z.object({ + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Trust level + */ + trustLevel: PluginTrustLevelSchema, + + /** + * Required permissions + */ + permissions: PermissionSetSchema, + + /** + * Sandbox configuration + */ + sandbox: SandboxConfigSchema, + + /** + * Security policy + */ + policy: SecurityPolicySchema.optional(), + + /** + * Security scan results + */ + scanResults: z.array(SecurityScanResultSchema).optional(), + + /** + * Known vulnerabilities + */ + vulnerabilities: z.array(SecurityVulnerabilitySchema).optional(), + + /** + * Code signing + */ + codeSigning: z.object({ + signed: z.boolean(), + signature: z.string().optional(), + certificate: z.string().optional(), + algorithm: z.string().optional(), + timestamp: z.string().datetime().optional(), + }).optional(), + + /** + * Security certifications + */ + certifications: z.array(z.object({ + name: z.string().describe('Certification name (e.g., SOC 2, ISO 27001)'), + issuer: z.string(), + issuedDate: z.string().datetime(), + expiryDate: z.string().datetime().optional(), + certificateUrl: z.string().url().optional(), + })).optional(), + + /** + * Security contact + */ + securityContact: z.object({ + email: z.string().email().optional(), + url: z.string().url().optional(), + pgpKey: z.string().optional(), + }).optional(), + + /** + * Vulnerability disclosure policy + */ + vulnerabilityDisclosure: z.object({ + policyUrl: z.string().url().optional(), + responseTime: z.number().int().optional().describe('Expected response time in hours'), + bugBounty: z.boolean().default(false), + }).optional(), +}); + +// Export types +export type PermissionScope = z.infer; +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; +export type SecurityPolicy = z.infer; +export type PluginTrustLevel = z.infer; +export type PluginSecurityManifest = z.infer; diff --git a/packages/spec/src/system/plugin-versioning.test.ts b/packages/spec/src/system/plugin-versioning.test.ts new file mode 100644 index 000000000..ec2c05cc5 --- /dev/null +++ b/packages/spec/src/system/plugin-versioning.test.ts @@ -0,0 +1,456 @@ +import { describe, expect, it } from 'vitest'; +import { + SemanticVersionSchema, + VersionConstraintSchema, + CompatibilityLevelSchema, + BreakingChangeSchema, + DeprecationNoticeSchema, + CompatibilityMatrixEntrySchema, + PluginCompatibilityMatrixSchema, + DependencyConflictSchema, + DependencyResolutionResultSchema, + MultiVersionSupportSchema, + PluginVersionMetadataSchema, +} from './plugin-versioning.zod'; + +describe('Plugin Versioning Schemas', () => { + describe('SemanticVersionSchema', () => { + it('should validate semantic version', () => { + const version = { + major: 1, + minor: 2, + patch: 3, + }; + const result = SemanticVersionSchema.parse(version); + expect(result.major).toBe(1); + expect(result.minor).toBe(2); + expect(result.patch).toBe(3); + }); + + it('should validate version with pre-release', () => { + const version = { + major: 2, + minor: 0, + patch: 0, + preRelease: 'beta.1', + }; + const result = SemanticVersionSchema.parse(version); + expect(result.preRelease).toBe('beta.1'); + }); + + it('should validate version with build metadata', () => { + const version = { + major: 1, + minor: 0, + patch: 0, + build: '20240203.1', + }; + const result = SemanticVersionSchema.parse(version); + expect(result.build).toBe('20240203.1'); + }); + + it('should reject negative version numbers', () => { + expect(() => SemanticVersionSchema.parse({ major: -1, minor: 0, patch: 0 })).toThrow(); + }); + }); + + describe('VersionConstraintSchema', () => { + it('should validate exact version', () => { + expect(() => VersionConstraintSchema.parse('1.2.3')).not.toThrow(); + }); + + it('should validate caret range', () => { + expect(() => VersionConstraintSchema.parse('^1.2.3')).not.toThrow(); + }); + + it('should validate tilde range', () => { + expect(() => VersionConstraintSchema.parse('~1.2.3')).not.toThrow(); + }); + + it('should validate comparison operators', () => { + expect(() => VersionConstraintSchema.parse('>=1.2.3')).not.toThrow(); + expect(() => VersionConstraintSchema.parse('>1.2.3')).not.toThrow(); + expect(() => VersionConstraintSchema.parse('<=1.2.3')).not.toThrow(); + expect(() => VersionConstraintSchema.parse('<1.2.3')).not.toThrow(); + }); + + it('should validate range', () => { + expect(() => VersionConstraintSchema.parse('1.2.3 - 2.3.4')).not.toThrow(); + }); + + it('should validate wildcards', () => { + expect(() => VersionConstraintSchema.parse('*')).not.toThrow(); + expect(() => VersionConstraintSchema.parse('latest')).not.toThrow(); + }); + }); + + describe('CompatibilityLevelSchema', () => { + it('should validate all compatibility levels', () => { + expect(() => CompatibilityLevelSchema.parse('fully-compatible')).not.toThrow(); + expect(() => CompatibilityLevelSchema.parse('backward-compatible')).not.toThrow(); + expect(() => CompatibilityLevelSchema.parse('deprecated-compatible')).not.toThrow(); + expect(() => CompatibilityLevelSchema.parse('breaking-changes')).not.toThrow(); + expect(() => CompatibilityLevelSchema.parse('incompatible')).not.toThrow(); + }); + }); + + describe('BreakingChangeSchema', () => { + it('should validate breaking change', () => { + const change = { + introducedIn: '2.0.0', + type: 'api-removed' as const, + description: 'Removed deprecated getUser method', + migrationGuide: 'Use getUserById instead', + deprecatedIn: '1.8.0', + removedIn: '2.0.0', + automatedMigration: true, + severity: 'major' as const, + }; + const result = BreakingChangeSchema.parse(change); + expect(result.type).toBe('api-removed'); + expect(result.severity).toBe('major'); + }); + + it('should validate all breaking change types', () => { + const types = [ + 'api-removed', + 'api-renamed', + 'api-signature-changed', + 'behavior-changed', + 'dependency-changed', + 'configuration-changed', + 'protocol-changed', + ] as const; + + types.forEach((type) => { + const change = { + introducedIn: '2.0.0', + type, + description: 'Test change', + severity: 'minor' as const, + }; + expect(() => BreakingChangeSchema.parse(change)).not.toThrow(); + }); + }); + }); + + describe('DeprecationNoticeSchema', () => { + it('should validate deprecation notice', () => { + const notice = { + feature: 'getUser', + deprecatedIn: '1.8.0', + removeIn: '2.0.0', + reason: 'Replaced with more efficient getUserById', + alternative: 'getUserById', + migrationPath: 'Replace getUser() calls with getUserById()', + }; + const result = DeprecationNoticeSchema.parse(notice); + expect(result.feature).toBe('getUser'); + expect(result.alternative).toBe('getUserById'); + }); + }); + + describe('CompatibilityMatrixEntrySchema', () => { + it('should validate compatibility matrix entry', () => { + const entry = { + from: '1.0.0', + to: '2.0.0', + compatibility: 'breaking-changes' as const, + breakingChanges: [ + { + introducedIn: '2.0.0', + type: 'api-removed' as const, + description: 'Removed old API', + severity: 'major' as const, + }, + ], + migrationRequired: true, + migrationComplexity: 'moderate' as const, + estimatedMigrationTime: 8, + migrationScript: './scripts/migrate-v1-to-v2.ts', + testCoverage: 95, + }; + const result = CompatibilityMatrixEntrySchema.parse(entry); + expect(result.compatibility).toBe('breaking-changes'); + expect(result.migrationRequired).toBe(true); + expect(result.estimatedMigrationTime).toBe(8); + }); + }); + + describe('PluginCompatibilityMatrixSchema', () => { + it('should validate plugin compatibility matrix', () => { + const matrix = { + pluginId: 'com.acme.plugin', + currentVersion: '2.0.0', + compatibilityMatrix: [ + { + from: '1.0.0', + to: '2.0.0', + compatibility: 'breaking-changes' as const, + migrationRequired: true, + }, + ], + supportedVersions: [ + { + version: '2.0.0', + supported: true, + securitySupport: true, + }, + { + version: '1.9.0', + supported: true, + securitySupport: true, + }, + { + version: '1.0.0', + supported: false, + endOfLife: new Date('2025-12-31').toISOString(), + securitySupport: false, + }, + ], + minimumCompatibleVersion: '1.8.0', + }; + const result = PluginCompatibilityMatrixSchema.parse(matrix); + expect(result.currentVersion).toBe('2.0.0'); + expect(result.supportedVersions).toHaveLength(3); + }); + }); + + describe('DependencyConflictSchema', () => { + it('should validate version mismatch conflict', () => { + const conflict = { + type: 'version-mismatch' as const, + plugins: [ + { + pluginId: 'com.acme.plugin-a', + version: '1.0.0', + requirement: '^2.0.0 of com.acme.shared', + }, + { + pluginId: 'com.acme.plugin-b', + version: '1.0.0', + requirement: '^1.0.0 of com.acme.shared', + }, + ], + description: 'Plugin A requires v2.x of shared, but Plugin B requires v1.x', + resolutions: [ + { + strategy: 'upgrade' as const, + description: 'Upgrade Plugin B to version that supports shared v2.x', + automaticResolution: false, + riskLevel: 'medium' as const, + }, + ], + severity: 'error' as const, + }; + const result = DependencyConflictSchema.parse(conflict); + expect(result.type).toBe('version-mismatch'); + expect(result.plugins).toHaveLength(2); + expect(result.resolutions).toHaveLength(1); + }); + + it('should validate circular dependency conflict', () => { + const conflict = { + type: 'circular-dependency' as const, + plugins: [ + { + pluginId: 'com.acme.plugin-a', + version: '1.0.0', + }, + { + pluginId: 'com.acme.plugin-b', + version: '1.0.0', + }, + ], + description: 'Plugin A depends on Plugin B, which depends on Plugin A', + severity: 'critical' as const, + }; + const result = DependencyConflictSchema.parse(conflict); + expect(result.type).toBe('circular-dependency'); + expect(result.severity).toBe('critical'); + }); + }); + + describe('DependencyResolutionResultSchema', () => { + it('should validate successful resolution', () => { + const result = { + success: true, + resolved: [ + { + pluginId: 'com.acme.plugin-a', + version: '^1.0.0', + resolvedVersion: '1.2.3', + }, + { + pluginId: 'com.acme.plugin-b', + version: '~2.0.0', + resolvedVersion: '2.0.5', + }, + ], + installationOrder: ['com.acme.plugin-b', 'com.acme.plugin-a'], + dependencyGraph: { + 'com.acme.plugin-a': ['com.acme.plugin-b'], + 'com.acme.plugin-b': [], + }, + }; + const parsed = DependencyResolutionResultSchema.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.resolved).toHaveLength(2); + expect(parsed.installationOrder).toEqual(['com.acme.plugin-b', 'com.acme.plugin-a']); + }); + + it('should validate failed resolution with conflicts', () => { + const result = { + success: false, + conflicts: [ + { + type: 'version-mismatch' as const, + plugins: [ + { pluginId: 'plugin-a', version: '1.0.0' }, + { pluginId: 'plugin-b', version: '1.0.0' }, + ], + description: 'Version conflict', + severity: 'error' as const, + }, + ], + warnings: ['Plugin C is deprecated'], + }; + const parsed = DependencyResolutionResultSchema.parse(result); + expect(parsed.success).toBe(false); + expect(parsed.conflicts).toHaveLength(1); + }); + }); + + describe('MultiVersionSupportSchema', () => { + it('should validate multi-version support with defaults', () => { + const config = MultiVersionSupportSchema.parse({}); + expect(config.enabled).toBe(false); + expect(config.maxConcurrentVersions).toBe(2); + expect(config.selectionStrategy).toBe('latest'); + }); + + it('should validate multi-version with routing', () => { + const config = { + enabled: true, + maxConcurrentVersions: 3, + selectionStrategy: 'custom' as const, + routing: [ + { + condition: 'tenant.isPremium', + version: '2.0.0', + priority: 100, + }, + { + condition: 'user.betaTester', + version: '2.1.0-beta', + priority: 200, + }, + ], + rollout: { + enabled: true, + strategy: 'canary' as const, + percentage: 10, + duration: 3600000, + }, + }; + const result = MultiVersionSupportSchema.parse(config); + expect(result.enabled).toBe(true); + expect(result.routing).toHaveLength(2); + expect(result.rollout?.strategy).toBe('canary'); + }); + }); + + describe('PluginVersionMetadataSchema', () => { + it('should validate complete version metadata', () => { + const metadata = { + pluginId: 'com.acme.plugin', + version: { + major: 1, + minor: 2, + patch: 3, + }, + versionString: '1.2.3', + releaseDate: new Date().toISOString(), + releaseNotes: 'Bug fixes and improvements', + breakingChanges: [ + { + introducedIn: '1.2.0', + type: 'api-signature-changed' as const, + description: 'Changed method signature', + severity: 'minor' as const, + }, + ], + securityFixes: [ + { + cve: 'CVE-2024-12345', + severity: 'high' as const, + description: 'XSS vulnerability', + fixedIn: '1.2.3', + }, + ], + statistics: { + downloads: 10000, + installations: 5000, + ratings: 4.5, + }, + support: { + status: 'active' as const, + securitySupport: true, + }, + }; + const result = PluginVersionMetadataSchema.parse(metadata); + expect(result.versionString).toBe('1.2.3'); + expect(result.statistics?.downloads).toBe(10000); + expect(result.support.status).toBe('active'); + }); + }); + + describe('Integration scenarios', () => { + it('should handle major version upgrade with breaking changes', () => { + const matrixEntry = CompatibilityMatrixEntrySchema.parse({ + from: '1.9.0', + to: '2.0.0', + compatibility: 'breaking-changes', + breakingChanges: [ + { + introducedIn: '2.0.0', + type: 'api-removed', + description: 'Removed legacy API', + severity: 'critical', + }, + ], + migrationRequired: true, + migrationComplexity: 'major', + estimatedMigrationTime: 40, + }); + expect(matrixEntry.migrationRequired).toBe(true); + expect(matrixEntry.migrationComplexity).toBe('major'); + }); + + it('should handle dependency conflict resolution', () => { + const resolution = DependencyResolutionResultSchema.parse({ + success: false, + conflicts: [ + { + type: 'incompatible-versions', + plugins: [ + { pluginId: 'plugin-a', version: '1.0.0' }, + { pluginId: 'plugin-b', version: '2.0.0' }, + ], + description: 'Incompatible versions', + resolutions: [ + { + strategy: 'upgrade', + description: 'Upgrade plugin-a', + automaticResolution: true, + riskLevel: 'low', + }, + ], + severity: 'warning', + }, + ], + }); + expect(resolution.conflicts?.[0].resolutions?.[0].strategy).toBe('upgrade'); + }); + }); +}); diff --git a/packages/spec/src/system/plugin-versioning.zod.ts b/packages/spec/src/system/plugin-versioning.zod.ts new file mode 100644 index 000000000..94884e627 --- /dev/null +++ b/packages/spec/src/system/plugin-versioning.zod.ts @@ -0,0 +1,466 @@ +import { z } from 'zod'; + +/** + * # Plugin Versioning and Compatibility Protocol + * + * Defines comprehensive versioning, compatibility checking, and dependency + * resolution mechanisms for the plugin ecosystem. + * + * Based on semantic versioning (SemVer) with extensions for: + * - Compatibility matrices + * - Breaking change detection + * - Migration paths + * - Multi-version support + */ + +/** + * Semantic Version Schema + * Standard SemVer format with optional pre-release and build metadata + */ +export const SemanticVersionSchema = z.object({ + major: z.number().int().min(0).describe('Major version (breaking changes)'), + minor: z.number().int().min(0).describe('Minor version (backward compatible features)'), + patch: z.number().int().min(0).describe('Patch version (backward compatible fixes)'), + preRelease: z.string().optional().describe('Pre-release identifier (alpha, beta, rc.1)'), + build: z.string().optional().describe('Build metadata'), +}).describe('Semantic version number'); + +/** + * Version Constraint Schema + * Defines version requirements using SemVer ranges + */ +export const VersionConstraintSchema = z.union([ + z.string().regex(/^[\d.]+$/).describe('Exact version: 1.2.3'), + z.string().regex(/^\^[\d.]+$/).describe('Compatible with: ^1.2.3 (>=1.2.3 <2.0.0)'), + z.string().regex(/^~[\d.]+$/).describe('Approximately: ~1.2.3 (>=1.2.3 <1.3.0)'), + z.string().regex(/^>=[\d.]+$/).describe('Greater than or equal: >=1.2.3'), + z.string().regex(/^>[\d.]+$/).describe('Greater than: >1.2.3'), + z.string().regex(/^<=[\d.]+$/).describe('Less than or equal: <=1.2.3'), + z.string().regex(/^<[\d.]+$/).describe('Less than: <1.2.3'), + z.string().regex(/^[\d.]+ - [\d.]+$/).describe('Range: 1.2.3 - 2.3.4'), + z.literal('*').describe('Any version'), + z.literal('latest').describe('Latest stable version'), +]); + +/** + * Compatibility Level + * Describes the level of compatibility between versions + */ +export const CompatibilityLevelSchema = z.enum([ + 'fully-compatible', // 100% compatible, drop-in replacement + 'backward-compatible', // Backward compatible, new features added + 'deprecated-compatible', // Compatible but uses deprecated features + 'breaking-changes', // Breaking changes, migration required + 'incompatible', // Completely incompatible +]).describe('Compatibility level between versions'); + +/** + * Breaking Change + * Documents a breaking change in a version + */ +export const BreakingChangeSchema = z.object({ + /** + * Version where the change was introduced + */ + introducedIn: z.string().describe('Version that introduced this breaking change'), + + /** + * Type of breaking change + */ + type: z.enum([ + 'api-removed', // API removed + 'api-renamed', // API renamed + 'api-signature-changed', // Function signature changed + 'behavior-changed', // Behavior changed + 'dependency-changed', // Dependency requirement changed + 'configuration-changed', // Configuration schema changed + 'protocol-changed', // Protocol implementation changed + ]), + + /** + * What was changed + */ + description: z.string(), + + /** + * Migration guide + */ + migrationGuide: z.string().optional().describe('How to migrate from old to new'), + + /** + * Deprecated in version + */ + deprecatedIn: z.string().optional().describe('Version where old API was deprecated'), + + /** + * Will be removed in version + */ + removedIn: z.string().optional().describe('Version where old API will be removed'), + + /** + * Automated migration available + */ + automatedMigration: z.boolean().default(false) + .describe('Whether automated migration tool is available'), + + /** + * Impact severity + */ + severity: z.enum(['critical', 'major', 'minor']).describe('Impact severity'), +}); + +/** + * Deprecation Notice + * Information about deprecated features + */ +export const DeprecationNoticeSchema = z.object({ + /** + * Feature or API being deprecated + */ + feature: z.string().describe('Deprecated feature identifier'), + + /** + * Version when deprecated + */ + deprecatedIn: z.string(), + + /** + * Planned removal version + */ + removeIn: z.string().optional(), + + /** + * Reason for deprecation + */ + reason: z.string(), + + /** + * Recommended alternative + */ + alternative: z.string().optional().describe('What to use instead'), + + /** + * Migration path + */ + migrationPath: z.string().optional().describe('How to migrate to alternative'), +}); + +/** + * Compatibility Matrix Entry + * Maps compatibility between different plugin versions + */ +export const CompatibilityMatrixEntrySchema = z.object({ + /** + * Source version + */ + from: z.string().describe('Version being upgraded from'), + + /** + * Target version + */ + to: z.string().describe('Version being upgraded to'), + + /** + * Compatibility level + */ + compatibility: CompatibilityLevelSchema, + + /** + * Breaking changes list + */ + breakingChanges: z.array(BreakingChangeSchema).optional(), + + /** + * Migration required + */ + migrationRequired: z.boolean().default(false), + + /** + * Migration complexity + */ + migrationComplexity: z.enum(['trivial', 'simple', 'moderate', 'complex', 'major']).optional(), + + /** + * Estimated migration time in hours + */ + estimatedMigrationTime: z.number().optional(), + + /** + * Migration script available + */ + migrationScript: z.string().optional().describe('Path to migration script'), + + /** + * Test coverage for migration + */ + testCoverage: z.number().min(0).max(100).optional() + .describe('Percentage of migration covered by tests'), +}); + +/** + * Plugin Compatibility Matrix + * Complete compatibility information for a plugin + */ +export const PluginCompatibilityMatrixSchema = z.object({ + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Current version + */ + currentVersion: z.string(), + + /** + * Compatibility entries + */ + compatibilityMatrix: z.array(CompatibilityMatrixEntrySchema), + + /** + * Supported versions + */ + supportedVersions: z.array(z.object({ + version: z.string(), + supported: z.boolean(), + endOfLife: z.string().datetime().optional().describe('End of support date'), + securitySupport: z.boolean().default(false).describe('Still receives security updates'), + })), + + /** + * Minimum compatible version + */ + minimumCompatibleVersion: z.string().optional() + .describe('Oldest version that can be directly upgraded'), +}); + +/** + * Dependency Conflict + * Represents a conflict in plugin dependencies + */ +export const DependencyConflictSchema = z.object({ + /** + * Type of conflict + */ + type: z.enum([ + 'version-mismatch', // Different versions required + 'missing-dependency', // Required dependency not found + 'circular-dependency', // Circular dependency detected + 'incompatible-versions', // Incompatible versions required by different plugins + 'conflicting-interfaces', // Plugins implement conflicting interfaces + ]), + + /** + * Plugins involved in conflict + */ + plugins: z.array(z.object({ + pluginId: z.string(), + version: z.string(), + requirement: z.string().optional().describe('What this plugin requires'), + })), + + /** + * Conflict description + */ + description: z.string(), + + /** + * Possible resolutions + */ + resolutions: z.array(z.object({ + strategy: z.enum([ + 'upgrade', // Upgrade one or more plugins + 'downgrade', // Downgrade one or more plugins + 'replace', // Replace with alternative plugin + 'disable', // Disable conflicting plugin + 'manual', // Manual intervention required + ]), + description: z.string(), + automaticResolution: z.boolean().default(false), + riskLevel: z.enum(['low', 'medium', 'high']), + })).optional(), + + /** + * Severity of conflict + */ + severity: z.enum(['critical', 'error', 'warning', 'info']), +}); + +/** + * Dependency Resolution Result + * Result of dependency resolution process + */ +export const DependencyResolutionResultSchema = z.object({ + /** + * Resolution successful + */ + success: z.boolean(), + + /** + * Resolved plugin versions + */ + resolved: z.array(z.object({ + pluginId: z.string(), + version: z.string(), + resolvedVersion: z.string(), + })).optional(), + + /** + * Conflicts found + */ + conflicts: z.array(DependencyConflictSchema).optional(), + + /** + * Warnings + */ + warnings: z.array(z.string()).optional(), + + /** + * Installation order (topologically sorted) + */ + installationOrder: z.array(z.string()).optional() + .describe('Plugin IDs in order they should be installed'), + + /** + * Dependency graph + */ + dependencyGraph: z.record(z.string(), z.array(z.string())).optional() + .describe('Map of plugin ID to its dependencies'), +}); + +/** + * Multi-Version Support Configuration + * Allows running multiple versions of a plugin simultaneously + */ +export const MultiVersionSupportSchema = z.object({ + /** + * Enable multi-version support + */ + enabled: z.boolean().default(false), + + /** + * Maximum concurrent versions + */ + maxConcurrentVersions: z.number().int().min(1).default(2) + .describe('How many versions can run at the same time'), + + /** + * Version selection strategy + */ + selectionStrategy: z.enum([ + 'latest', // Always use latest version + 'stable', // Use latest stable version + 'compatible', // Use version compatible with dependencies + 'pinned', // Use pinned version + 'canary', // Use canary/preview version + 'custom', // Custom selection logic + ]).default('latest'), + + /** + * Version routing rules + */ + routing: z.array(z.object({ + condition: z.string().describe('Routing condition (e.g., tenant, user, feature flag)'), + version: z.string().describe('Version to use when condition matches'), + priority: z.number().int().default(100).describe('Rule priority'), + })).optional(), + + /** + * Gradual rollout configuration + */ + rollout: z.object({ + enabled: z.boolean().default(false), + strategy: z.enum(['percentage', 'blue-green', 'canary']), + percentage: z.number().min(0).max(100).optional() + .describe('Percentage of traffic to new version'), + duration: z.number().int().optional() + .describe('Rollout duration in milliseconds'), + }).optional(), +}); + +/** + * Plugin Version Metadata + * Complete version information for a plugin + */ +export const PluginVersionMetadataSchema = z.object({ + /** + * Plugin identifier + */ + pluginId: z.string(), + + /** + * Version number + */ + version: SemanticVersionSchema, + + /** + * Version string (computed) + */ + versionString: z.string().describe('Full version string (e.g., 1.2.3-beta.1+build.123)'), + + /** + * Release date + */ + releaseDate: z.string().datetime(), + + /** + * Release notes + */ + releaseNotes: z.string().optional(), + + /** + * Breaking changes + */ + breakingChanges: z.array(BreakingChangeSchema).optional(), + + /** + * Deprecations + */ + deprecations: z.array(DeprecationNoticeSchema).optional(), + + /** + * Compatibility matrix + */ + compatibilityMatrix: z.array(CompatibilityMatrixEntrySchema).optional(), + + /** + * Security vulnerabilities fixed + */ + securityFixes: z.array(z.object({ + cve: z.string().optional().describe('CVE identifier'), + severity: z.enum(['critical', 'high', 'medium', 'low']), + description: z.string(), + fixedIn: z.string().describe('Version where vulnerability was fixed'), + })).optional(), + + /** + * Download statistics + */ + statistics: z.object({ + downloads: z.number().int().min(0).optional(), + installations: z.number().int().min(0).optional(), + ratings: z.number().min(0).max(5).optional(), + }).optional(), + + /** + * Support status + */ + support: z.object({ + status: z.enum(['active', 'maintenance', 'deprecated', 'eol']), + endOfLife: z.string().datetime().optional(), + securitySupport: z.boolean().default(true), + }), +}); + +// Export types +export type SemanticVersion = z.infer; +export type VersionConstraint = z.infer; +export type CompatibilityLevel = z.infer; +export type BreakingChange = z.infer; +export type DeprecationNotice = z.infer; +export type CompatibilityMatrixEntry = z.infer; +export type PluginCompatibilityMatrix = z.infer; +export type DependencyConflict = z.infer; +export type DependencyResolutionResult = z.infer; +export type MultiVersionSupport = z.infer; +export type PluginVersionMetadata = z.infer;