# GooseFactory — Interface Contracts > **Version:** 1.0.0 > **Last Updated:** 2025-07-14 > **Author:** Architect Agent > **Status:** CANONICAL — All agents build to these types. Every boundary between agents is defined here. If it's not in this document, it's not a contract. If two agents disagree, this document wins. --- ## Table of Contents 1. [API Contract (REST + WebSocket)](#1-api-contract) 2. [MCP Tool Schemas](#2-mcp-tool-schemas) 3. [Modal Data Contract](#3-modal-data-contract) 4. [UI Component Contracts](#4-ui-component-contracts) 5. [Rust Backend Contracts](#5-rust-backend-contracts) 6. [Learning System Contracts](#6-learning-system-contracts) 7. [Cross-Cutting Contracts](#7-cross-cutting-contracts) --- ## 1. API Contract ### 1.1 Base Configuration ```typescript // Base URL const API_BASE = "https://api.goosefactory.dev/v1"; const WS_BASE = "wss://api.goosefactory.dev/v1/ws"; // All requests require: // Authorization: Bearer // Content-Type: application/json // X-Request-Id: (optional, for tracing) // All responses include: // X-Request-Id: // X-API-Version: "1" ``` ### 1.2 Authentication Types ```typescript // === JWT Payload === interface JWTPayload { sub: string; // user ID (UUID) email: string; role: Role; scopes: Scope[]; iat: number; // issued at (unix) exp: number; // expires (unix, +15min) } type Role = "owner" | "admin" | "operator" | "viewer" | "agent"; type Scope = | "pipelines:read" | "pipelines:write" | "tasks:read" | "tasks:approve" | "tasks:reject" | "deploy:staging" | "deploy:production" | "agents:manage" | "assets:read" | "assets:write" | "audit:read" | "feedback:read" | "feedback:write" | "learning:read" | "learning:write"; // === API Key (server-to-server) === // Header: X-API-Key: // Keys are scoped to specific Scope[] subsets // Keys are rotatable via POST /v1/auth/keys/rotate // === Auth Endpoints === // POST /v1/auth/login → { access_token, refresh_token, expires_in } // POST /v1/auth/refresh → { access_token, expires_in } // POST /v1/auth/logout → 204 // POST /v1/auth/keys → { api_key, key_id, scopes } // DELETE /v1/auth/keys/:id → 204 ``` ### 1.3 Standard Response Envelope ```typescript // === Success Response === interface ApiResponse { ok: true; data: T; meta?: ResponseMeta; } interface ResponseMeta { requestId: string; timestamp: string; // ISO 8601 pagination?: PaginationMeta; rateLimit?: RateLimitMeta; } interface PaginationMeta { page: number; limit: number; total: number; totalPages: number; hasNext: boolean; hasPrev: boolean; } interface RateLimitMeta { limit: number; remaining: number; resetAt: string; // ISO 8601 } // === Error Response === interface ApiError { ok: false; error: { code: ErrorCode; message: string; // Human-readable details?: Record; // Machine-readable context field?: string; // For validation errors requestId: string; }; } type ErrorCode = // 4xx Client Errors | "BAD_REQUEST" // 400 — malformed input | "VALIDATION_ERROR" // 400 — schema validation failed | "UNAUTHORIZED" // 401 — missing/invalid token | "FORBIDDEN" // 403 — insufficient scopes | "NOT_FOUND" // 404 — entity doesn't exist | "CONFLICT" // 409 — state conflict (e.g., already approved) | "GONE" // 410 — entity archived/deleted | "RATE_LIMITED" // 429 — too many requests // 5xx Server Errors | "INTERNAL_ERROR" // 500 — unexpected failure | "SERVICE_UNAVAILABLE" // 503 — dependency down | "TIMEOUT" // 504 — upstream timeout ; // === Pagination Request === interface PaginationParams { page?: number; // default: 1 limit?: number; // default: 20, max: 100 sort?: string; // field name, prefix with - for desc: "-created_at" } ``` ### 1.4 Core Entity Types ```typescript // ═══════════════════════════════════════ // PIPELINE // ═══════════════════════════════════════ interface Pipeline { id: string; // UUID name: string; slug: string; // URL-safe unique identifier template: PipelineTemplate; platform: string; // e.g., "go-high-level", "shopify" // State currentStage: PipelineStage; status: PipelineStatus; priority: Priority; // Ownership createdBy: string; // user ID assigneeId: string | null; // user ID // Config config: Record; metadata: Record; // SLA slaDeadline: string | null; // ISO 8601 startedAt: string; completedAt: string | null; // Timestamps createdAt: string; updatedAt: string; } type PipelineStatus = "active" | "paused" | "completed" | "failed" | "archived"; type Priority = "critical" | "high" | "medium" | "low"; type PipelineTemplate = "mcp-server-standard" | "mcp-server-minimal" | "mcp-server-enterprise"; type PipelineStage = | "intake" | "scaffolding" | "building" | "testing" | "review" // ★ HUMAN GATE | "staging" // ★ HUMAN GATE | "production" // ★ HUMAN GATE | "published"; // ═══════════════════════════════════════ // PIPELINE STAGE RECORD // ═══════════════════════════════════════ interface PipelineStageRecord { id: string; pipelineId: string; stageName: PipelineStage; stageOrder: number; status: StageStatus; requiresApproval: boolean; approvalType: "manual" | "auto" | "conditional"; autoAdvance: boolean; validationRules: ValidationRule[]; enteredAt: string | null; completedAt: string | null; durationSeconds: number | null; createdAt: string; } type StageStatus = "pending" | "active" | "completed" | "skipped" | "failed"; interface ValidationRule { type: "test_coverage" | "tests_passing" | "required_asset" | "approval_count" | "custom"; config: Record; // e.g., { type: "test_coverage", config: { minPercent: 80 } } // e.g., { type: "required_asset", config: { assetType: "readme" } } } // ═══════════════════════════════════════ // TASK (Decision Queue Item) // ═══════════════════════════════════════ interface Task { id: string; // UUID pipelineId: string | null; stageName: PipelineStage | null; // Details type: TaskType; title: string; description: string | null; context: TaskContext; // Queue status: TaskStatus; priority: Priority; // Assignment assigneeId: string | null; claimedAt: string | null; claimedBy: string | null; // Decision decision: TaskDecision | null; decisionNotes: string | null; decisionData: Record; decidedAt: string | null; decidedBy: string | null; // SLA slaDeadline: string | null; slaWarningsSent: number; slaBreached: boolean; escalationLevel: number; // 0 = none, 1-4 = escalation tiers // Blocking blocksStageAdvance: boolean; blocksPipelineId: string | null; // Timestamps createdAt: string; updatedAt: string; } type TaskType = | "approval" // Gate approval (stage transition) | "review" // Code/design/doc review | "decision" // Choose between options | "manual_action" // Something only a human can do | "fix_required"; // Something broke, needs intervention type TaskStatus = | "pending" // Waiting for someone to act | "claimed" // Someone is looking at it | "in_progress" // Active work happening | "completed" // Decision made | "expired" // SLA breached, auto-escalated | "escalated"; // Bumped to higher authority type TaskDecision = "approved" | "rejected" | "deferred" | "escalated"; interface TaskContext { // What's being decided summary: string; details?: string; // Supporting data testResults?: TestResults; diffSummary?: DiffSummary; metrics?: Record; relatedAssets?: string[]; // asset IDs // For modals modalType?: string; // Which HITL modal to show modalConfig?: Record; } interface TestResults { total: number; passed: number; failed: number; skipped: number; coveragePercent: number; failureDetails?: TestFailure[]; } interface TestFailure { testName: string; error: string; file?: string; line?: number; } interface DiffSummary { filesChanged: number; insertions: number; deletions: number; files: DiffFile[]; } interface DiffFile { path: string; status: "added" | "modified" | "deleted" | "renamed"; insertions: number; deletions: number; patch?: string; // Unified diff format } // ═══════════════════════════════════════ // APPROVAL // ═══════════════════════════════════════ interface Approval { id: string; taskId: string; pipelineId: string; stageName: PipelineStage; type: ApprovalType; status: ApprovalStatus; approvedBy: string | null; approvedAt: string | null; rejectionReason: string | null; conditions: string[]; requiredApprovers: number; currentApprovers: number; createdAt: string; } type ApprovalType = "stage_gate" | "deploy" | "code_review" | "manual_check"; type ApprovalStatus = "pending" | "approved" | "rejected" | "expired"; // ═══════════════════════════════════════ // AGENT // ═══════════════════════════════════════ interface Agent { id: string; name: string; type: AgentType; status: AgentStatus; currentTaskId: string | null; currentPipelineId: string | null; health: AgentHealth; capabilities: string[]; config: Record; tasksCompletedTotal: number; avgTaskDurationSeconds: number | null; createdAt: string; updatedAt: string; } type AgentType = "ai_builder" | "test_runner" | "deployer" | "monitor"; type AgentStatus = "idle" | "active" | "error" | "offline"; interface AgentHealth { uptimeSeconds: number; tasksCompleted24h: number; errorRate: number; // 0.0 - 1.0 lastHeartbeat: string; // ISO 8601 } // ═══════════════════════════════════════ // ASSET // ═══════════════════════════════════════ interface Asset { id: string; pipelineId: string; stageName: PipelineStage | null; type: AssetType; name: string; path: string | null; storageKey: string; storageBucket: string | null; sizeBytes: number; contentType: string; checksumSha256: string; version: number; previousVersionId: string | null; generatedBy: string; // user or agent ID generatorType: "user" | "agent" | "ci"; metadata: Record; createdAt: string; } type AssetType = "code" | "config" | "docs" | "build" | "test_report" | "screenshot"; // ═══════════════════════════════════════ // NOTIFICATION // ═══════════════════════════════════════ interface Notification { id: string; userId: string; type: NotificationType; title: string; body: string | null; data: Record; channels: NotificationChannel[]; deliveredVia: NotificationChannel[]; read: boolean; readAt: string | null; dismissed: boolean; entityType: string | null; entityId: string | null; actionUrl: string | null; createdAt: string; } type NotificationType = | "task_assigned" | "approval_pending" | "sla_warning" | "sla_breach" | "deploy_status" | "pipeline_completed" | "agent_error" | "feedback_processed"; type NotificationChannel = "dashboard" | "discord" | "email" | "sms" | "push"; // ═══════════════════════════════════════ // AUDIT LOG ENTRY // ═══════════════════════════════════════ interface AuditEntry { id: string; actorType: "user" | "agent" | "system" | "webhook"; actorId: string | null; actorName: string | null; action: string; // e.g., "pipeline.created", "task.approved" entityType: string; // e.g., "pipeline", "task", "approval" entityId: string; changes: Record; metadata: Record; createdAt: string; } ``` ### 1.5 REST Endpoints ```typescript // ═══════════════════════════════════════ // PIPELINES // ═══════════════════════════════════════ // GET /v1/pipelines interface ListPipelinesParams extends PaginationParams { status?: PipelineStatus; stage?: PipelineStage; priority?: Priority; assigneeId?: string; search?: string; } // Response: ApiResponse // POST /v1/pipelines interface CreatePipelineBody { name: string; template?: PipelineTemplate; // default: "mcp-server-standard" platform: string; config?: Record; priority?: Priority; // default: "medium" assigneeId?: string; } // Response: ApiResponse (201) // GET /v1/pipelines/:pipelineId // Response: ApiResponse // PATCH /v1/pipelines/:pipelineId interface UpdatePipelineBody { priority?: Priority; status?: PipelineStatus; config?: Record; assigneeId?: string | null; metadata?: Record; } // Response: ApiResponse // DELETE /v1/pipelines/:pipelineId?archive=true // Response: 204 // GET /v1/pipelines/:pipelineId/stages // Response: ApiResponse // POST /v1/pipelines/:pipelineId/stages/advance interface AdvanceStageBody { targetStage?: PipelineStage; // default: next in sequence skipValidation?: boolean; // default: false (admin only) notes?: string; } // Response: ApiResponse<{ stage: PipelineStageRecord; tasksCreated: Task[] }> // ═══════════════════════════════════════ // TASKS (Decision Queue) // ═══════════════════════════════════════ // GET /v1/tasks interface ListTasksParams extends PaginationParams { status?: TaskStatus; priority?: Priority; type?: TaskType; pipelineId?: string; assigneeId?: string; // use "me" for current user slaBreached?: boolean; } // Response: ApiResponse // GET /v1/tasks/:taskId // Response: ApiResponse // POST /v1/tasks/:taskId/claim // Response: ApiResponse // POST /v1/tasks/:taskId/complete interface CompleteTaskBody { decision: TaskDecision; notes?: string; decisionData?: Record; } // Response: ApiResponse // POST /v1/tasks/:taskId/reassign interface ReassignTaskBody { assigneeId: string; reason?: string; } // Response: ApiResponse // POST /v1/tasks/bulk interface BulkTaskBody { action: "approve" | "reject" | "defer"; taskIds: string[]; notes?: string; } // Response: ApiResponse<{ succeeded: string[]; failed: Array<{ taskId: string; error: string }> }> // GET /v1/tasks/stats // Response: ApiResponse interface TaskStats { pending: number; inProgress: number; blocked: number; avgWaitTimeMinutes: number; slaBreaches: number; byPriority: Record; byType: Record; } // ═══════════════════════════════════════ // APPROVALS // ═══════════════════════════════════════ // GET /v1/approvals interface ListApprovalsParams extends PaginationParams { status?: ApprovalStatus; pipelineId?: string; assignee?: string; // "me" for current user } // Response: ApiResponse // GET /v1/approvals/:approvalId // Response: ApiResponse // POST /v1/approvals/:approvalId/approve interface ApproveBody { notes?: string; conditions?: string[]; } // Response: ApiResponse // POST /v1/approvals/:approvalId/reject interface RejectBody { reason: string; // required requestedChanges?: string[]; } // Response: ApiResponse // POST /v1/approvals/:approvalId/escalate interface EscalateBody { escalateTo?: string; // user ID reason: string; } // Response: ApiResponse // ═══════════════════════════════════════ // AGENTS // ═══════════════════════════════════════ // GET /v1/agents // Response: ApiResponse // GET /v1/agents/:agentId // Response: ApiResponse // POST /v1/agents/:agentId/heartbeat interface HeartbeatBody { status: AgentStatus; metrics?: Record; currentTaskId?: string | null; } // Response: 204 // GET /v1/agents/:agentId/activity interface AgentActivityParams { since?: string; // ISO 8601 limit?: number; // default: 50 } // Response: ApiResponse // ═══════════════════════════════════════ // ASSETS // ═══════════════════════════════════════ // GET /v1/pipelines/:pipelineId/assets interface ListAssetsParams extends PaginationParams { type?: AssetType; stage?: PipelineStage; } // Response: ApiResponse // POST /v1/assets (multipart/form-data) interface CreateAssetBody { pipelineId: string; stage?: PipelineStage; type: AssetType; file: File; // binary upload metadata?: Record; } // Response: ApiResponse (201) // GET /v1/assets/:assetId // Response: ApiResponse // GET /v1/assets/:assetId/download // Response: binary stream with Content-Type and Content-Disposition headers // GET /v1/assets/:assetId/diff interface DiffParams { fromVersion: number; toVersion?: number; // default: latest } // Response: ApiResponse // ═══════════════════════════════════════ // NOTIFICATIONS // ═══════════════════════════════════════ // GET /v1/notifications interface ListNotificationsParams extends PaginationParams { unread?: boolean; type?: NotificationType; } // Response: ApiResponse // POST /v1/notifications/:id/read // Response: 204 // POST /v1/notifications/read-all // Response: 204 // ═══════════════════════════════════════ // WEBHOOKS // ═══════════════════════════════════════ // POST /v1/webhooks interface CreateWebhookBody { url: string; events: WebSocketEventType[]; secret: string; } // Response: ApiResponse (201) // GET /v1/webhooks // Response: ApiResponse // DELETE /v1/webhooks/:id // Response: 204 // GET /v1/webhooks/events // Response: ApiResponse // ═══════════════════════════════════════ // AUDIT LOG // ═══════════════════════════════════════ // GET /v1/audit interface AuditParams extends PaginationParams { entityType?: string; entityId?: string; actorId?: string; action?: string; since?: string; // ISO 8601 until?: string; // ISO 8601 } // Response: ApiResponse // ═══════════════════════════════════════ // FEEDBACK (Learning System) // ═══════════════════════════════════════ // POST /v1/feedback // Body: FeedbackEvent (see §3.2) // Response: ApiResponse<{ id: string }> (201) // GET /v1/feedback interface FeedbackParams extends PaginationParams { pipelineId?: string; modalType?: string; since?: string; decision?: string; } // Response: ApiResponse // GET /v1/feedback/stats // Response: ApiResponse (see §6) // ═══════════════════════════════════════ // DASHBOARD (aggregated data) // ═══════════════════════════════════════ // GET /v1/dashboard/summary // Response: ApiResponse interface DashboardSummary { pipelines: { active: number; paused: number; completed: number; failed: number; }; tasks: TaskStats; agents: { total: number; active: number; idle: number; error: number; offline: number; }; recentActivity: AuditEntry[]; // last 20 events slaStatus: { onTrack: number; warning: number; breached: number; }; } ``` ### 1.6 WebSocket Contract ```typescript // ═══════════════════════════════════════ // CONNECTION // ═══════════════════════════════════════ // Connect: wss://api.goosefactory.dev/v1/ws?token= // On connect error: { type: "error", error: { code: "UNAUTHORIZED", message: "..." } } // ═══════════════════════════════════════ // CLIENT → SERVER MESSAGES // ═══════════════════════════════════════ interface WsSubscribe { type: "subscribe"; channels: WsChannel[]; } interface WsUnsubscribe { type: "unsubscribe"; channels: WsChannel[]; } interface WsPing { type: "ping"; } type WsChannel = | "pipeline:*" // All pipeline events | `pipeline:${string}` // Specific pipeline by ID | "tasks:*" // All task events | "tasks:pending" // Only pending task events | "agents:*" // All agent events | "agents:health" // Only health/heartbeat events | "deploys:*" // All deploy events | "notifications:me" // Current user's notifications ; // ═══════════════════════════════════════ // SERVER → CLIENT MESSAGES // ═══════════════════════════════════════ interface WsEvent { type: WebSocketEventType; timestamp: string; // ISO 8601 channel: WsChannel; data: T; } interface WsSubscribed { type: "subscribed"; channels: WsChannel[]; } interface WsPong { type: "pong"; } // ═══════════════════════════════════════ // EVENT TYPES AND PAYLOADS // ═══════════════════════════════════════ type WebSocketEventType = // Pipeline lifecycle | "pipeline.created" | "pipeline.updated" | "pipeline.stage_changed" | "pipeline.completed" | "pipeline.failed" | "pipeline.blocked" // Task lifecycle | "task.created" | "task.claimed" | "task.completed" | "task.sla_warning" | "task.sla_breached" | "task.escalated" // Approval lifecycle | "approval.pending" | "approval.approved" | "approval.rejected" // Agent status | "agent.status_changed" | "agent.heartbeat" // Asset events | "asset.created" | "asset.updated" // Deploy events | "deploy.started" | "deploy.succeeded" | "deploy.failed" // Feedback events | "feedback.received" | "feedback.processed" ; // Event payload types: interface PipelineCreatedPayload { pipeline: Pipeline; } interface PipelineStageChangedPayload { pipelineId: string; pipelineName: string; fromStage: PipelineStage; toStage: PipelineStage; triggeredBy: string; // user or agent ID } interface PipelineBlockedPayload { pipelineId: string; pipelineName: string; blockedAt: PipelineStage; reason: string; taskId: string; // The task that's blocking } interface TaskCreatedPayload { task: Task; } interface TaskCompletedPayload { task: Task; decision: TaskDecision; decidedBy: string; } interface TaskSlaPayload { taskId: string; taskTitle: string; pipelineId: string | null; slaDeadline: string; minutesRemaining: number; // negative if breached escalationLevel: number; } interface AgentStatusPayload { agentId: string; agentName: string; fromStatus: AgentStatus; toStatus: AgentStatus; currentTaskId: string | null; } interface AgentHeartbeatPayload { agentId: string; agentName: string; status: AgentStatus; health: AgentHealth; } interface DeployPayload { pipelineId: string; pipelineName: string; target: "staging" | "production"; version: string; status: "started" | "succeeded" | "failed"; url?: string; // Deploy URL on success error?: string; // Error message on failure } interface FeedbackReceivedPayload { feedbackId: string; pipelineId: string | null; modalType: string; decision: string | null; } ``` --- ## 2. MCP Tool Schemas ### 2.1 Tool Definitions Every tool follows MCP SDK conventions: name, description, inputSchema (JSON Schema), and returns content blocks. ```typescript // ═══════════════════════════════════════ // TOOL: factory_get_pending_tasks // ═══════════════════════════════════════ const factory_get_pending_tasks = { name: "factory_get_pending_tasks", description: "Get all tasks requiring human attention, sorted by priority and SLA urgency. " + "This is the operator's decision inbox — the most important view in the factory.", inputSchema: { type: "object" as const, properties: { pipeline_id: { type: "string", description: "Filter tasks to a specific pipeline (UUID)", }, priority: { type: "string", enum: ["critical", "high", "medium", "low"], description: "Filter by priority level", }, assignee: { type: "string", description: "Filter by assignee. Use 'me' for current user.", }, include_context: { type: "boolean", description: "Include full task context (diffs, test results). Default: true", default: true, }, limit: { type: "number", description: "Maximum tasks to return. Default: 20", default: 20, }, }, required: [], }, }; // Return: text content with formatted task list + task count summary // ═══════════════════════════════════════ // TOOL: factory_approve_task // ═══════════════════════════════════════ const factory_approve_task = { name: "factory_approve_task", description: "Approve a pending task or approval gate, allowing the pipeline to proceed. " + "Use this when the operator says 'approve', 'LGTM', 'ship it', etc.", inputSchema: { type: "object" as const, properties: { task_id: { type: "string", description: "UUID of the task to approve", }, notes: { type: "string", description: "Optional approval notes or conditions", }, conditions: { type: "array", items: { type: "string" }, description: "Conditions that must be met post-approval", }, }, required: ["task_id"], }, }; // Return: text confirming approval + what pipeline action was triggered // ═══════════════════════════════════════ // TOOL: factory_reject_task // ═══════════════════════════════════════ const factory_reject_task = { name: "factory_reject_task", description: "Reject a pending task with feedback. Sends pipeline back for rework. " + "Use when the operator says 'reject', 'needs work', 'fix this', etc.", inputSchema: { type: "object" as const, properties: { task_id: { type: "string", description: "UUID of the task to reject", }, reason: { type: "string", description: "Why this was rejected — required for feedback loop", }, requested_changes: { type: "array", items: { type: "string" }, description: "Specific changes needed before re-submission", }, severity: { type: "string", enum: ["minor", "major", "critical"], description: "How serious the issues are. Default: major", default: "major", }, }, required: ["task_id", "reason"], }, }; // Return: text confirming rejection + which stage the pipeline reverted to // ═══════════════════════════════════════ // TOOL: factory_get_pipeline_status // ═══════════════════════════════════════ const factory_get_pipeline_status = { name: "factory_get_pipeline_status", description: "Get current state of all pipelines or a specific pipeline. " + "Shows stage, progress, blockers, and timeline.", inputSchema: { type: "object" as const, properties: { pipeline_id: { type: "string", description: "Specific pipeline UUID. Omit for all active pipelines.", }, status: { type: "string", enum: ["active", "paused", "completed", "failed", "all"], description: "Filter by status. Default: active", default: "active", }, include_details: { type: "boolean", description: "Include tasks, assets, and stage history. Default: false", default: false, }, }, required: [], }, }; // ═══════════════════════════════════════ // TOOL: factory_advance_stage // ═══════════════════════════════════════ const factory_advance_stage = { name: "factory_advance_stage", description: "Manually advance a pipeline to its next stage. Triggers validation checks. " + "If validation fails, returns the failures instead of advancing.", inputSchema: { type: "object" as const, properties: { pipeline_id: { type: "string", description: "UUID of the pipeline to advance", }, target_stage: { type: "string", enum: ["intake", "scaffolding", "building", "testing", "review", "staging", "production", "published"], description: "Target stage. Default: next in sequence", }, skip_validation: { type: "boolean", description: "Skip gate checks. Admin/owner only. Default: false", default: false, }, notes: { type: "string", description: "Notes for the stage transition", }, }, required: ["pipeline_id"], }, }; // ═══════════════════════════════════════ // TOOL: factory_assign_priority // ═══════════════════════════════════════ const factory_assign_priority = { name: "factory_assign_priority", description: "Set or change priority on a pipeline or task. Recalculates SLA deadlines.", inputSchema: { type: "object" as const, properties: { entity_type: { type: "string", enum: ["pipeline", "task"], }, entity_id: { type: "string", description: "UUID of the pipeline or task", }, priority: { type: "string", enum: ["critical", "high", "medium", "low"], }, reason: { type: "string", description: "Why the priority changed", }, }, required: ["entity_type", "entity_id", "priority"], }, }; // ═══════════════════════════════════════ // TOOL: factory_get_blockers // ═══════════════════════════════════════ const factory_get_blockers = { name: "factory_get_blockers", description: "Get all items that are blocked and why. The 'what's stuck' view. " + "Includes suggested actions to unblock.", inputSchema: { type: "object" as const, properties: { pipeline_id: { type: "string", description: "Filter to a specific pipeline", }, include_suggestions: { type: "boolean", description: "Include AI-generated suggestions for unblocking. Default: true", default: true, }, }, required: [], }, }; // Return type: interface BlockerInfo { blockerType: "task" | "stage" | "agent" | "dependency"; entityId: string; title: string; pipelineId: string; pipelineName: string; priority: Priority; hoursBlocked: number; blockReason: string; suggestedAction?: string; } // ═══════════════════════════════════════ // TOOL: factory_run_tests // ═══════════════════════════════════════ const factory_run_tests = { name: "factory_run_tests", description: "Trigger test suite for a pipeline's current build.", inputSchema: { type: "object" as const, properties: { pipeline_id: { type: "string", description: "UUID of the pipeline", }, test_type: { type: "string", enum: ["unit", "integration", "e2e", "all"], description: "Which tests to run. Default: all", default: "all", }, environment: { type: "string", enum: ["local", "ci"], description: "Where to run. Default: ci", default: "ci", }, }, required: ["pipeline_id"], }, }; // ═══════════════════════════════════════ // TOOL: factory_deploy // ═══════════════════════════════════════ const factory_deploy = { name: "factory_deploy", description: "Deploy a pipeline's build to staging or production. " + "Production requires prior staging deploy + approval.", inputSchema: { type: "object" as const, properties: { pipeline_id: { type: "string", description: "UUID of the pipeline to deploy", }, target: { type: "string", enum: ["staging", "production"], }, version: { type: "string", description: "Specific version. Default: latest build", }, dry_run: { type: "boolean", description: "Preview what would happen without deploying. Default: false", default: false, }, }, required: ["pipeline_id", "target"], }, }; // ═══════════════════════════════════════ // TOOL: factory_search // ═══════════════════════════════════════ const factory_search = { name: "factory_search", description: "Search across pipelines, tasks, assets, and audit logs.", inputSchema: { type: "object" as const, properties: { query: { type: "string", description: "Search query text", }, entity_types: { type: "array", items: { type: "string", enum: ["pipeline", "task", "asset", "audit"] }, description: "Which entity types to search. Default: all", }, limit: { type: "number", description: "Max results. Default: 10", default: 10, }, }, required: ["query"], }, }; // ═══════════════════════════════════════ // TOOL: factory_create_pipeline // ═══════════════════════════════════════ const factory_create_pipeline = { name: "factory_create_pipeline", description: "Initialize a new MCP server pipeline from a template.", inputSchema: { type: "object" as const, properties: { name: { type: "string", description: "Pipeline name (e.g., 'ghl-mcp-server')", }, platform: { type: "string", description: "Target platform (e.g., 'go-high-level', 'shopify')", }, template: { type: "string", enum: ["mcp-server-standard", "mcp-server-minimal", "mcp-server-enterprise"], description: "Pipeline template. Default: mcp-server-standard", default: "mcp-server-standard", }, priority: { type: "string", enum: ["critical", "high", "medium", "low"], description: "Initial priority. Default: medium", default: "medium", }, }, required: ["name", "platform"], }, }; ``` ### 2.2 MCP Resource URIs ```typescript // ═══════════════════════════════════════ // RESOURCES (read-only, subscribable) // ═══════════════════════════════════════ // Static resources (fixed URI): // factory://dashboard/summary // Returns: DashboardSummary (see §1.5) // MIME: application/json // Updates: on any pipeline/task/agent event // factory://config/templates // Returns: PipelineTemplateConfig[] // MIME: application/json // Updates: rarely (config changes) interface PipelineTemplateConfig { id: PipelineTemplate; name: string; description: string; stages: PipelineStage[]; defaultConfig: Record; requiredAssets: AssetType[]; validationRules: ValidationRule[]; } // Dynamic resources (parameterized URI): // factory://pipelines/{pipelineId}/state // Returns: PipelineDetailState // MIME: application/json // Updates: on pipeline.* events for this pipeline interface PipelineDetailState { pipeline: Pipeline; stages: PipelineStageRecord[]; currentTasks: Task[]; recentAssets: Asset[]; blockers: BlockerInfo[]; timeline: AuditEntry[]; // last 50 events for this pipeline } // factory://servers/{serverName}/status // Returns: ServerStatus // MIME: application/json interface ServerStatus { name: string; pipelineId: string; stage: PipelineStage; health: "healthy" | "degraded" | "failing" | "unknown"; deployments: { staging: DeploymentInfo | null; production: DeploymentInfo | null; }; lastTestRun: TestResults | null; metrics: Record; } interface DeploymentInfo { version: string; deployedAt: string; url: string; status: "running" | "stopped" | "failed"; } // factory://pipelines/{pipelineId}/test-results // Returns: TestResults (see §1.4) // MIME: application/json // factory://pipelines/{pipelineId}/build-logs // Returns: string (raw build output) // MIME: text/plain ``` ### 2.3 MCP Prompts ```typescript // ═══════════════════════════════════════ // PROMPT: review_server // ═══════════════════════════════════════ const review_server = { name: "review_server", description: "Pull all context needed to review an MCP server: code quality, " + "test results, docs, deployment readiness. Returns a structured review prompt.", arguments: [ { name: "server_name", description: "Name of the MCP server to review", required: true, }, ], }; // Returns: messages[] containing pipeline status, test results, asset inventory, // open tasks, quality standards comparison, review checklist // ═══════════════════════════════════════ // PROMPT: whats_needs_attention // ═══════════════════════════════════════ const whats_needs_attention = { name: "whats_needs_attention", description: "Summary of everything needing human attention right now, prioritized by urgency.", arguments: [ { name: "scope", description: "Filter scope: 'all' | 'my-tasks' | 'critical-only'. Default: all", required: false, }, ], }; // Returns: messages[] with SLA breaches, pending approvals, blocked pipelines, // failed tests/deploys, agent health issues, suggested next actions // ═══════════════════════════════════════ // PROMPT: deploy_checklist // ═══════════════════════════════════════ const deploy_checklist = { name: "deploy_checklist", description: "Pre-deployment review checklist for an MCP server.", arguments: [ { name: "pipeline_id", description: "UUID of the pipeline being deployed", required: true, }, { name: "target", description: "'staging' or 'production'", required: true, }, ], }; // Returns: messages[] with structured checklist: // tests, code review, README, TOOLS.md, env vars, secrets, perf, rollback plan // ═══════════════════════════════════════ // PROMPT: pipeline_retrospective // ═══════════════════════════════════════ const pipeline_retrospective = { name: "pipeline_retrospective", description: "Generate a retrospective analysis of a completed pipeline.", arguments: [ { name: "pipeline_id", description: "UUID of the completed pipeline", required: true, }, ], }; // Returns: messages[] with timeline, bottlenecks, quality metrics, lessons learned ``` ### 2.4 MCP App Hosting Contract ```typescript // ═══════════════════════════════════════ // MCP APP REGISTRATION // ═══════════════════════════════════════ // MCP tools can return UI content using Goose's MCP UI rendering system. // The Factory MCP server uses this for rich HITL modals. // Tool response format for rendering a modal: interface McpToolResponseWithUI { _meta: { goose: { toolUI: { displayType: "inline" | "sidecar"; // inline = in chat, sidecar = side panel name: string; // Display name for the UI renderer: "mcp-ui"; // Must be "mcp-ui" }; }; }; content: McpUIResource[]; } // UI resource format (one per content block): interface McpUIResource { uri: string; // e.g., "ui://hitl-modal" mimeType: "text/html"; // Always HTML for our modals text: string; // The HTML content (self-contained) } // The tool creates the resource using @mcp-ui/client: // createUIResource({ // uri: "ui://factory-modal", // content: { type: "rawHtml", htmlString: modalHtml }, // encoding: "text", // }) // ═══════════════════════════════════════ // MODAL REGISTRATION (internal to MCP server) // ═══════════════════════════════════════ // Each modal type is registered with its capabilities: interface ModalRegistration { id: string; // e.g., "traffic-light" name: string; // e.g., "Traffic Light" description: string; version: string; // semver // When to use this modal suitableFor: ModalUseCase[]; // What data it needs requiredContext: ModalContextField[]; optionalContext: ModalContextField[]; // What data it produces outputSchema: { feedbackTypes: FeedbackType[]; capturesMetrics: string[]; // e.g., ["response_time", "hover_journey"] }; // Display preferences display: { preferredType: "inline" | "sidecar"; minWidth: number; // pixels minHeight: number; supportsDarkMode: boolean; supportsMobile: boolean; }; } type ModalUseCase = | "binary_decision" // pass/fail, approve/reject | "batch_review" // multiple items at once | "multi_dimensional" // rate across multiple axes | "comparison" // A vs B | "subjective" // emotional/gut reaction | "high_stakes" // critical decisions | "annotation" // mark specific regions | "estimation" // effort/risk estimation | "triage" // multi-pipeline overview | "retrospective" // reflection/improvement | "verification"; // checklist confirmation type ModalContextField = | "pipeline_id" | "pipeline_name" | "item_id" | "item_name" | "deliverable_preview" | "deliverable_content" | "test_results" | "diff_summary" | "metrics" | "items_batch" // for batch modals: array of items | "content_before" // for comparison modals | "content_after" | "checklist_items" // for verification modals | "estimation_question" | "persona" // for voice-of-customer | "dimensions" // for multi-dimensional modals ; ``` --- ## 3. Modal Data Contract ### 3.1 Host → Modal Communication (Context Injection) ```typescript // The MCP App iframe host passes context data INTO the modal via URL parameters // and a global JavaScript object injected before the modal HTML loads. // ═══════════════════════════════════════ // URL PARAMETERS (simple values) // ═══════════════════════════════════════ // The modal's src URL includes query params: // ui://factory-modal?pipeline_id=xxx&item_id=yyy&modal_type=traffic-light // ═══════════════════════════════════════ // INJECTED CONTEXT OBJECT (complex data) // ═══════════════════════════════════════ // The host injects a global object BEFORE the modal renders. // The modal accesses it via window.__FACTORY_CONTEXT__ interface FactoryModalContext { // Identity modalType: string; // e.g., "traffic-light" modalVersion: string; // semver sessionId: string; // Factory session ID // What's being reviewed pipelineId: string; pipelineName: string; itemId: string; itemName: string; // Content (varies by modal type) deliverablePreview?: string; // Short text preview deliverableContent?: string; // Full content (code, text, etc.) contentBefore?: string; // For comparison modals contentAfter?: string; // Supporting data testResults?: TestResults; diffSummary?: DiffSummary; metrics?: Record; // Batch data (for multi-item modals) items?: ModalItem[]; // Configuration dimensions?: ModalDimension[]; // For multi-dimensional modals checklistItems?: ChecklistItem[]; // For verification modals estimationQuestion?: string; persona?: ModalPersona; timerSeconds?: number; // For timed modals // Theme theme: "dark" | "light"; accentColor: string; // hex color } interface ModalItem { id: string; name: string; preview: string; content?: string; metadata?: Record; } interface ModalDimension { id: string; // e.g., "code_quality" name: string; // e.g., "Code Quality" description?: string; weight: number; // Importance multiplier (1-3) } interface ChecklistItem { id: string; label: string; description?: string; aiAssessment?: string; // What the AI thinks about this item aiPasses?: boolean; // AI's pre-assessment } interface ModalPersona { name: string; role: string; context: string; avatarEmoji: string; } ``` ### 3.2 Modal → Host Communication (postMessage API) ```typescript // ═══════════════════════════════════════ // EVERY modal posts decisions back via: // window.parent.postMessage(message, "*"); // ═══════════════════════════════════════ // === Discriminated Union of all message types === type ModalMessage = | ModalResponse | ModalReady | ModalResize | ModalClose | ModalError; // ─── READY (modal loaded and interactive) ─── interface ModalReady { type: "factory_modal_ready"; modalType: string; version: string; } // ─── RESIZE (modal wants to change size) ─── interface ModalResize { type: "factory_modal_resize"; width?: number; height: number; } // ─── CLOSE (modal wants to close without submitting) ─── interface ModalClose { type: "factory_modal_close"; reason: "cancelled" | "timeout" | "error"; } // ─── ERROR (modal hit an error) ─── interface ModalError { type: "factory_modal_error"; error: string; details?: unknown; } // ─── RESPONSE (the main data submission) ─── interface ModalResponse { type: "factory_modal_response"; // Identity modalType: string; // Which modal was used modalVersion: string; pipelineId: string; itemId: string; sessionId: string; // Timing timestamp: string; // ISO 8601 responseTimeMs: number; // Time from modal open to submission // The feedback data (see FeedbackEvent below) feedback: FeedbackPayload; // Hidden behavioral metrics (ALWAYS collected) meta: ModalMetrics; } // ═══════════════════════════════════════ // FEEDBACK PAYLOAD // (the meaningful data from the modal) // ═══════════════════════════════════════ interface FeedbackPayload { // Decision (present in most modals) decision?: DecisionFeedback; // Scores (multi-dimensional modals) scores?: DimensionScore[]; // Free text freeText?: FreeTextFeedback; // Comparison (A/B modals) comparison?: ComparisonFeedback; // Annotations (spotlight/code review) annotations?: Annotation[]; // Confidence (confidence meter or paired) confidence?: ConfidenceFeedback; // Estimation (priority poker) estimation?: EstimationFeedback; // Batch decisions (swipe/speed round) batchDecisions?: BatchDecision[]; // Retrospective (start/stop/continue) retrospective?: RetrospectiveFeedback; // Ranking (ranking arena) ranking?: RankingFeedback; // Checklist (checklist ceremony) checklist?: ChecklistFeedback; // Temperature (thermometer) temperature?: TemperatureFeedback; // Custom data (any modal-specific data) custom?: Record; } // ═══════════════════════════════════════ // THE 8 FEEDBACK TYPES (atomic units) // ═══════════════════════════════════════ // 1. DECISION interface DecisionFeedback { decision: "approved" | "rejected" | "needs_work" | "deferred" | "skipped"; reason?: string; severity?: "minor" | "moderate" | "major" | "critical"; blockers?: string[]; suggestions?: string[]; tags?: string[]; // e.g., ["naming", "logic", "style"] } // 2. DIMENSION SCORE interface DimensionScore { dimension: QualityDimension; score: number; // 1-10 (integer or float) weight?: number; // How much the operator cares (learned) comment?: string; } type QualityDimension = | "code_quality" | "design_aesthetics" | "design_ux" | "test_coverage" | "documentation" | "architecture" | "error_handling" | "performance" | "security" | "creativity" | "completeness" | "naming" | "dx"; // 3. FREE TEXT interface FreeTextFeedback { liked?: string; disliked?: string; generalNotes?: string; } // 4. COMPARISON interface ComparisonFeedback { preferred: "A" | "B" | "neither" | "both_good"; reason?: string; preferenceStrength: "slight" | "moderate" | "strong" | "overwhelming"; winningFactors?: string[]; perDimension?: Array<{ dimension: QualityDimension; winner: "A" | "B" | "tie"; }>; timeOnA_ms?: number; timeOnB_ms?: number; } // 5. ANNOTATION interface Annotation { type: "praise" | "criticism" | "question" | "suggestion"; target: { file?: string; lineStart?: number; lineEnd?: number; charStart?: number; charEnd?: number; selector?: string; // CSS selector for design region?: string; // Named region }; text: string; severity?: "nit" | "minor" | "major" | "critical"; category?: string; } // 6. CONFIDENCE interface ConfidenceFeedback { confidencePercent: number; // 0-100 zone: "guess" | "educated_guess" | "confident" | "certain"; wouldDelegateToAI: boolean; needsExpertReview: boolean; lowConfidenceReason?: string; isTrainingExample?: boolean; // High-confidence decisions can train } // 7. ESTIMATION interface EstimationFeedback { estimate: number | "unknown" | "break"; // Fibonacci: 1,2,3,5,8,13,21 context?: string; hoveredValues?: number[]; // Which cards were considered } // 8. BATCH DECISION interface BatchDecision { itemId: string; decision: "approve" | "reject" | "love" | "skip"; timeMs: number; // Time spent on this item flippedForDetails: boolean; // Did they look at full details? } // ─── COMPOSITE TYPES ─── interface RankingFeedback { finalRanking: string[]; // Ordered array of item IDs (best first) rankingChanges: number; // Number of reorder operations lockedItems: string[]; // Items operator was most confident about expandedItems: string[]; // Items that needed closer inspection perItemNotes?: Record; } interface RetrospectiveFeedback { start: RetrospectiveNote[]; stop: RetrospectiveNote[]; continue_: RetrospectiveNote[]; // "continue" is reserved word, use continue_ } interface RetrospectiveNote { text: string; priority?: "urgent" | "important" | "idea"; } interface ChecklistFeedback { checks: Record; // { criterionId: checked } notes: Record; // { criterionId: note } for unchecked items checkOrder: string[]; // Order items were checked allClear: boolean; completionRate: number; // 0-1 } interface TemperatureFeedback { temperature: number; // 0-100 zone: "freezing" | "cold" | "lukewarm" | "warm" | "hot" | "blazing"; drivers: string[]; // What drove the rating dragJourney: number[]; // Temperature values during drag } // ═══════════════════════════════════════ // HIDDEN BEHAVIORAL METRICS // (collected by every modal automatically) // ═══════════════════════════════════════ interface ModalMetrics { // Timing timeToFirstInteractionMs: number; timeToDecisionMs: number; // Engagement fieldsModified: string[]; scrollDepth?: number; // 0-1 (how much content was viewed) revisits: number; // Times the modal was re-focused totalInteractions: number; // Click/tap count // Context viewportSize: { width: number; height: number }; deviceType: "desktop" | "mobile"; // Hover/attention data (modal-specific) hoverJourney?: Array<{ target: string; durationMs: number }>; hesitationPoints?: Array<{ target: string; durationMs: number }>; } ``` ### 3.3 FeedbackEvent (Full Storage Schema) ```typescript // The complete event stored by the learning system. // Combines ModalResponse with enriched metadata. interface FeedbackEvent { // Identity id: string; // UUID v7 (time-sortable) timestamp: string; // ISO 8601 sessionId: string; modalType: string; modalVersion: string; // What was reviewed workProduct: WorkProductRef; pipelineId: string | null; pipelineStage: PipelineStage | null; mcpServerType?: string; // The feedback (from ModalResponse.feedback) feedback: FeedbackPayload; // Behavioral metrics (from ModalResponse.meta) meta: ModalMetrics & EnrichedMeta; } interface WorkProductRef { type: "mcp_server" | "code_module" | "design" | "test_suite" | "documentation" | "pipeline_config"; id: string; version: string; // Git SHA or version tag path?: string; summary: string; bubaConfidence: number; // 0-1: AI confidence before review bubaScorePrediction?: number; // 1-10: predicted human score generationContext: { promptHash: string; memorySnapshot: string[]; modelUsed: string; generationTimeMs: number; iterationCount: number; }; } interface EnrichedMeta { timeOfDay: "morning" | "afternoon" | "evening" | "night"; dayOfWeek: string; sessionFatigue: number; // Nth modal in this session concurrentModals: number; // NLP enrichment (filled by processing pipeline) extractedThemes?: string[]; sentiment?: number; // -1 to 1 actionableItems?: string[]; predictionDelta?: number; // actual - predicted score } ``` --- ## 4. UI Component Contracts ### 4.1 Decision Queue Sidebar ```typescript // ═══════════════════════════════════════ // DecisionQueue Component // Location: packages/desktop/ui/desktop/src/components/factory/DecisionQueue.tsx // ═══════════════════════════════════════ interface DecisionQueueProps { // Data tasks: Task[]; selectedTaskId: string | null; // Loading/error state isLoading: boolean; error: string | null; // Callbacks onSelectTask: (taskId: string) => void; onApproveTask: (taskId: string) => void; onRejectTask: (taskId: string, reason: string) => void; onDeferTask: (taskId: string) => void; onBatchApprove: (taskIds: string[]) => void; onRefresh: () => void; // Filters (controlled) filters: DecisionQueueFilters; onFiltersChange: (filters: DecisionQueueFilters) => void; } interface DecisionQueueFilters { priority?: Priority; type?: TaskType; slaBreached?: boolean; search?: string; } // ─── Data shape for queue items ─── interface DecisionQueueItem { id: string; title: string; type: TaskType; priority: Priority; pipelineName: string; stageName: PipelineStage; // SLA slaDeadline: string | null; slaBreached: boolean; minutesWaiting: number; // Quick context previewText: string; testSummary?: string; // e.g., "47/47 ✅" // Status indicators isSelected: boolean; isBatchSelected: boolean; createdAt: string; } // ─── Keyboard shortcuts ─── // j / ArrowDown → select next task // k / ArrowUp → select previous task // a → approve selected task // r → open reject dialog for selected // d → defer selected task // Enter → open context panel for selected // Shift+a → toggle batch select // Ctrl+Shift+a → batch approve all selected ``` ### 4.2 Context Panel ```typescript // ═══════════════════════════════════════ // ContextPanel Component // Location: packages/desktop/ui/desktop/src/components/factory/ContextPanel.tsx // ═══════════════════════════════════════ interface ContextPanelProps { // Data task: Task | null; pipeline: Pipeline | null; stages: PipelineStageRecord[]; assets: Asset[]; auditTrail: AuditEntry[]; // Loading isLoading: boolean; // Actions onApprove: (notes?: string, conditions?: string[]) => void; onReject: (reason: string, requestedChanges?: string[]) => void; onDefer: () => void; onEscalate: (reason: string) => void; onRunTests: () => void; onViewDiff: (assetId: string) => void; onOpenModal: (modalType: string) => void; // Tab navigation activeTab: ContextTab; onTabChange: (tab: ContextTab) => void; } type ContextTab = "overview" | "diff" | "tests" | "assets" | "timeline" | "modal"; // The context panel renders different content based on activeTab: // - "overview" → Task summary, pipeline state, key metrics // - "diff" → Code diff viewer (DiffSummary rendered) // - "tests" → Test results detail (TestResults rendered) // - "assets" → Asset list with download links // - "timeline" → Audit trail for this pipeline // - "modal" → Embedded HITL modal (iframe) ``` ### 4.3 Pipeline Kanban ```typescript // ═══════════════════════════════════════ // PipelineKanban Component // Location: packages/desktop/ui/desktop/src/components/factory/PipelineKanban.tsx // ═══════════════════════════════════════ interface PipelineKanbanProps { // Data pipelines: Pipeline[]; // Callbacks onSelectPipeline: (pipelineId: string) => void; onAdvanceStage: (pipelineId: string, targetStage: PipelineStage) => void; onChangePriority: (pipelineId: string, priority: Priority) => void; // View options groupBy: "stage" | "priority" | "assignee"; onGroupByChange: (groupBy: string) => void; } // ─── Kanban stages (columns) ─── const KANBAN_STAGES: KanbanStageConfig[] = [ { stage: "intake", label: "📥 Intake", color: "#666" }, { stage: "scaffolding", label: "🏗️ Scaffolding", color: "#888" }, { stage: "building", label: "🔨 Building", color: "#4488FF" }, { stage: "testing", label: "🧪 Testing", color: "#FF8844" }, { stage: "review", label: "👁️ Review ★", color: "#FFD700", isGate: true }, { stage: "staging", label: "🚀 Staging ★", color: "#00AAFF", isGate: true }, { stage: "production", label: "🌍 Production ★", color: "#FF4444", isGate: true }, { stage: "published", label: "✅ Published", color: "#00CC55" }, ]; interface KanbanStageConfig { stage: PipelineStage; label: string; color: string; isGate?: boolean; // Requires human approval } // ─── Kanban card data shape ─── interface KanbanCard { id: string; // pipeline ID name: string; platform: string; priority: Priority; status: PipelineStatus; // Progress currentStage: PipelineStage; stageIndex: number; // 0-7 progressPercent: number; // 0-100 // Indicators hasBlockers: boolean; hasSlaWarning: boolean; hasSlaBreached: boolean; pendingTaskCount: number; // Quick stats testsPassing?: string; // "47/47" or "38/47" coveragePercent?: number; lastActivity: string; // ISO 8601 assigneeName?: string; } ``` ### 4.4 MCP App Iframe Host ```typescript // ═══════════════════════════════════════ // McpAppHost Component // Location: packages/desktop/ui/desktop/src/components/factory/McpAppHost.tsx // ═══════════════════════════════════════ interface McpAppHostProps { // What to render htmlContent: string; // Self-contained HTML from MCP tool response context: FactoryModalContext; // Injected into iframe as window.__FACTORY_CONTEXT__ // Display displayType: "inline" | "sidecar"; maxWidth?: number; maxHeight?: number; // Lifecycle callbacks onReady: () => void; // Modal sent factory_modal_ready onResponse: (response: ModalResponse) => void; // Modal submitted feedback onResize: (height: number) => void; // Modal requested resize onClose: (reason: string) => void; // Modal requested close onError: (error: string) => void; // Modal reported error } // ─── Host implementation requirements ─── // 1. Create a sandboxed iframe: //