109 KiB

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)
  2. MCP Tool Schemas
  3. Modal Data Contract
  4. UI Component Contracts
  5. Rust Backend Contracts
  6. Learning System Contracts
  7. Cross-Cutting Contracts

1. API Contract

1.1 Base Configuration

// Base URL
const API_BASE = "https://api.goosefactory.dev/v1";
const WS_BASE = "wss://api.goosefactory.dev/v1/ws";

// All requests require:
// Authorization: Bearer <jwt>
// Content-Type: application/json
// X-Request-Id: <uuid> (optional, for tracing)

// All responses include:
// X-Request-Id: <uuid>
// X-API-Version: "1"

1.2 Authentication Types

// === 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: <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

// === Success Response ===
interface ApiResponse<T> {
  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<string, unknown>;  // 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

// ═══════════════════════════════════════
// 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<string, unknown>;
  metadata: Record<string, unknown>;

  // 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<string, unknown>;
  // 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<string, unknown>;
  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<string, number>;
  relatedAssets?: string[];    // asset IDs

  // For modals
  modalType?: string;          // Which HITL modal to show
  modalConfig?: Record<string, unknown>;
}

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<string, unknown>;

  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<string, unknown>;
  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<string, unknown>;

  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<string, { from: unknown; to: unknown }>;
  metadata: Record<string, unknown>;

  createdAt: string;
}

1.5 REST Endpoints

// ═══════════════════════════════════════
// PIPELINES
// ═══════════════════════════════════════

// GET /v1/pipelines
interface ListPipelinesParams extends PaginationParams {
  status?: PipelineStatus;
  stage?: PipelineStage;
  priority?: Priority;
  assigneeId?: string;
  search?: string;
}
// Response: ApiResponse<Pipeline[]>

// POST /v1/pipelines
interface CreatePipelineBody {
  name: string;
  template?: PipelineTemplate;         // default: "mcp-server-standard"
  platform: string;
  config?: Record<string, unknown>;
  priority?: Priority;                 // default: "medium"
  assigneeId?: string;
}
// Response: ApiResponse<Pipeline> (201)

// GET /v1/pipelines/:pipelineId
// Response: ApiResponse<Pipeline>

// PATCH /v1/pipelines/:pipelineId
interface UpdatePipelineBody {
  priority?: Priority;
  status?: PipelineStatus;
  config?: Record<string, unknown>;
  assigneeId?: string | null;
  metadata?: Record<string, unknown>;
}
// Response: ApiResponse<Pipeline>

// DELETE /v1/pipelines/:pipelineId?archive=true
// Response: 204

// GET /v1/pipelines/:pipelineId/stages
// Response: ApiResponse<PipelineStageRecord[]>

// 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<Task[]>

// GET /v1/tasks/:taskId
// Response: ApiResponse<Task>

// POST /v1/tasks/:taskId/claim
// Response: ApiResponse<Task>

// POST /v1/tasks/:taskId/complete
interface CompleteTaskBody {
  decision: TaskDecision;
  notes?: string;
  decisionData?: Record<string, unknown>;
}
// Response: ApiResponse<Task>

// POST /v1/tasks/:taskId/reassign
interface ReassignTaskBody {
  assigneeId: string;
  reason?: string;
}
// Response: ApiResponse<Task>

// 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<TaskStats>

interface TaskStats {
  pending: number;
  inProgress: number;
  blocked: number;
  avgWaitTimeMinutes: number;
  slaBreaches: number;
  byPriority: Record<Priority, number>;
  byType: Record<TaskType, number>;
}

// ═══════════════════════════════════════
// APPROVALS
// ═══════════════════════════════════════

// GET /v1/approvals
interface ListApprovalsParams extends PaginationParams {
  status?: ApprovalStatus;
  pipelineId?: string;
  assignee?: string;                   // "me" for current user
}
// Response: ApiResponse<Approval[]>

// GET /v1/approvals/:approvalId
// Response: ApiResponse<Approval>

// POST /v1/approvals/:approvalId/approve
interface ApproveBody {
  notes?: string;
  conditions?: string[];
}
// Response: ApiResponse<Approval>

// POST /v1/approvals/:approvalId/reject
interface RejectBody {
  reason: string;                      // required
  requestedChanges?: string[];
}
// Response: ApiResponse<Approval>

// POST /v1/approvals/:approvalId/escalate
interface EscalateBody {
  escalateTo?: string;                 // user ID
  reason: string;
}
// Response: ApiResponse<Approval>

// ═══════════════════════════════════════
// AGENTS
// ═══════════════════════════════════════

// GET /v1/agents
// Response: ApiResponse<Agent[]>

// GET /v1/agents/:agentId
// Response: ApiResponse<Agent>

// POST /v1/agents/:agentId/heartbeat
interface HeartbeatBody {
  status: AgentStatus;
  metrics?: Record<string, number>;
  currentTaskId?: string | null;
}
// Response: 204

// GET /v1/agents/:agentId/activity
interface AgentActivityParams {
  since?: string;                      // ISO 8601
  limit?: number;                      // default: 50
}
// Response: ApiResponse<AuditEntry[]>

// ═══════════════════════════════════════
// ASSETS
// ═══════════════════════════════════════

// GET /v1/pipelines/:pipelineId/assets
interface ListAssetsParams extends PaginationParams {
  type?: AssetType;
  stage?: PipelineStage;
}
// Response: ApiResponse<Asset[]>

// POST /v1/assets (multipart/form-data)
interface CreateAssetBody {
  pipelineId: string;
  stage?: PipelineStage;
  type: AssetType;
  file: File;                          // binary upload
  metadata?: Record<string, unknown>;
}
// Response: ApiResponse<Asset> (201)

// GET /v1/assets/:assetId
// Response: ApiResponse<Asset>

// 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<DiffSummary>

// ═══════════════════════════════════════
// NOTIFICATIONS
// ═══════════════════════════════════════

// GET /v1/notifications
interface ListNotificationsParams extends PaginationParams {
  unread?: boolean;
  type?: NotificationType;
}
// Response: ApiResponse<Notification[]>

// 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<Webhook> (201)

// GET /v1/webhooks
// Response: ApiResponse<Webhook[]>

// DELETE /v1/webhooks/:id
// Response: 204

// GET /v1/webhooks/events
// Response: ApiResponse<WebSocketEventType[]>

// ═══════════════════════════════════════
// 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<AuditEntry[]>

// ═══════════════════════════════════════
// 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<FeedbackEvent[]>

// GET /v1/feedback/stats
// Response: ApiResponse<FeedbackStats> (see §6)

// ═══════════════════════════════════════
// DASHBOARD (aggregated data)
// ═══════════════════════════════════════

// GET /v1/dashboard/summary
// Response: ApiResponse<DashboardSummary>

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

// ═══════════════════════════════════════
// CONNECTION
// ═══════════════════════════════════════

// Connect: wss://api.goosefactory.dev/v1/ws?token=<jwt>
// 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<T = unknown> {
  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.

// ═══════════════════════════════════════
// 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

// ═══════════════════════════════════════
// 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<string, unknown>;
  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<string, number>;
}

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

// ═══════════════════════════════════════
// 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

// ═══════════════════════════════════════
// 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)

// 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<string, number>;
  
  // 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<string, unknown>;
}

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)

// ═══════════════════════════════════════
// 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<string, unknown>;
}

// ═══════════════════════════════════════
// 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<string, string>;
}

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<string, boolean>;     // { criterionId: checked }
  notes: Record<string, string>;       // { 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)

// 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

// ═══════════════════════════════════════
// 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

// ═══════════════════════════════════════
// 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

// ═══════════════════════════════════════
// 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

// ═══════════════════════════════════════
// 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:
//    <iframe sandbox="allow-scripts allow-forms" srcdoc={processedHtml} />
//
// 2. Inject context BEFORE modal HTML:
//    Prepend: <script>window.__FACTORY_CONTEXT__ = ${JSON.stringify(context)};</script>
//
// 3. Listen for postMessage events:
//    window.addEventListener("message", (event) => {
//      if (event.data.type === "factory_modal_response") { onResponse(event.data); }
//      if (event.data.type === "factory_modal_ready") { onReady(); }
//      if (event.data.type === "factory_modal_resize") { onResize(event.data.height); }
//      if (event.data.type === "factory_modal_close") { onClose(event.data.reason); }
//      if (event.data.type === "factory_modal_error") { onError(event.data.error); }
//    });
//
// 4. On factory_modal_response:
//    a. Validate response against FeedbackPayload schema
//    b. Forward to API: POST /v1/feedback
//    c. Close the modal iframe
//    d. Advance to next task in queue
//
// 5. On timeout (no response within configurable limit):
//    a. Post "factory_modal_timeout" into the iframe
//    b. Wait 5s for graceful close
//    c. Force close and mark task as "deferred"

4.5 WebSocket Client

// ═══════════════════════════════════════
// useFactoryWebSocket Hook
// Location: packages/desktop/ui/desktop/src/hooks/useWebSocket.ts
// ═══════════════════════════════════════

interface UseFactoryWebSocketOptions {
  token: string;
  channels: WsChannel[];
  onEvent: (event: WsEvent) => void;
  
  // Reconnection
  maxRetries?: number;                 // default: 10
  retryDelayMs?: number;               // default: 1000, doubles each retry
  maxRetryDelayMs?: number;            // default: 30000
}

interface UseFactoryWebSocketReturn {
  isConnected: boolean;
  subscribe: (channels: WsChannel[]) => void;
  unsubscribe: (channels: WsChannel[]) => void;
  disconnect: () => void;
}

// ─── How the WebSocket client updates state ───

// The hook is used in a FactoryContext provider that maintains all factory state.
// On each WsEvent, the provider updates its state:

type FactoryAction =
  | { type: "PIPELINE_UPDATED"; pipeline: Pipeline }
  | { type: "TASK_CREATED"; task: Task }
  | { type: "TASK_COMPLETED"; task: Task }
  | { type: "TASK_SLA_WARNING"; payload: TaskSlaPayload }
  | { type: "AGENT_STATUS"; payload: AgentStatusPayload }
  | { type: "DEPLOY_UPDATE"; payload: DeployPayload }
  | { type: "NOTIFICATION"; notification: Notification }
  | { type: "SET_TASKS"; tasks: Task[] }
  | { type: "SET_PIPELINES"; pipelines: Pipeline[] }
  | { type: "SET_AGENTS"; agents: Agent[] }
  ;

interface FactoryState {
  // Core data
  pipelines: Pipeline[];
  tasks: Task[];
  agents: Agent[];
  notifications: Notification[];
  
  // Derived
  pendingTaskCount: number;
  slaBreachCount: number;
  
  // Connection
  wsConnected: boolean;
  
  // Loading
  isInitialLoading: boolean;
  lastUpdated: string;
}

// Event → State mapping:
//
// "pipeline.created"      → add to pipelines[]
// "pipeline.stage_changed"→ update pipeline in pipelines[]
// "pipeline.completed"    → update pipeline status
// "task.created"          → add to tasks[], increment pendingTaskCount
// "task.completed"        → update task in tasks[], decrement pendingTaskCount
// "task.sla_warning"      → update task slaWarning flag
// "task.sla_breached"     → update task, increment slaBreachCount
// "agent.status_changed"  → update agent in agents[]
// "deploy.succeeded"      → update pipeline deployments
// "feedback.received"     → (optional) show toast notification

5. Rust Backend Contracts

5.1 Factory Extension Registration

// ═══════════════════════════════════════
// How the Factory extension registers with goosed
// Location: crates/goose-mcp/src/factory/mod.rs
// ═══════════════════════════════════════

// The Factory MCP Server is registered as a built-in extension
// in the goosed binary, similar to how "developer" and "memory"
// extensions are registered.

// In crates/goose-mcp/src/lib.rs, add:
// pub mod factory;

// In ui/desktop/src/built-in-extensions.json, add:
{
  "id": "factory",
  "name": "Factory Command Center",
  "description": "AI Factory pipeline management — decision queue, approvals, and deployments",
  "enabled": true,
  "type": "builtin"
}
// The Factory extension provides:
// - 11 MCP tools (§2.1)
// - 6 MCP resources (§2.2)
// - 4 MCP prompts (§2.3)
// - MCP App UI rendering for HITL modals (§2.4)

// Extension config in ~/.config/goose/config.yaml:
# Factory extension configuration
extensions:
  factory:
    name: Factory Command Center
    type: builtin
    enabled: true
    config:
      api_base_url: "https://api.goosefactory.dev/v1"
      ws_url: "wss://api.goosefactory.dev/v1/ws"
      # Auth: uses the user's JWT from the desktop app login
      default_priority: "medium"
      auto_advance_low_risk: true
      sla_defaults:
        critical: 3600        # 1 hour
        high: 14400            # 4 hours
        medium: 86400          # 24 hours
        low: 259200            # 72 hours

5.2 Permission/Approval Flow Modifications

// ═══════════════════════════════════════
// GooseFactory modifies Goose's built-in permission system
// to route factory operations through the approval queue.
// ═══════════════════════════════════════

// Goose permission modes (existing):
// - auto:           Full autonomy
// - approve:        Confirm before any tool use
// - smart_approve:  Risk-based (auto-approve low-risk, flag high-risk)
// - chat:           No tool use

// GooseFactory additions:
// - factory_smart:  Like smart_approve but routes high-risk decisions
//                   through the Factory decision queue instead of inline prompts.

// Risk classification for factory tools:

interface FactoryToolRiskClassification {
  // LOW RISK — auto-execute in factory_smart mode
  lowRisk: [
    "factory_get_pending_tasks",
    "factory_get_pipeline_status",
    "factory_get_blockers",
    "factory_search",
  ];

  // MEDIUM RISK — queue for review but don't block
  mediumRisk: [
    "factory_approve_task",    // Approving items
    "factory_reject_task",     // Rejecting items
    "factory_advance_stage",   // Stage advancement
    "factory_assign_priority", // Priority changes
    "factory_run_tests",       // Triggering tests
    "factory_create_pipeline", // Creating new pipelines
  ];

  // HIGH RISK — always require explicit human approval
  highRisk: [
    "factory_deploy",         // Deployment (especially production)
  ];
}

// The permission check flow:
//
// 1. Tool call received by goosed
// 2. goosed checks permission mode (factory_smart)
// 3. If lowRisk → execute immediately
// 4. If mediumRisk → execute, log to audit, notify operator
// 5. If highRisk → create Task in decision queue, block until approved
// 6. For "factory_deploy" with target="production":
//    → ALWAYS creates a Task with type="approval"
//    → ALWAYS requires explicit human approval
//    → Approval task includes deploy checklist (prompt: deploy_checklist)

5.3 Config File Format Changes

# ═══════════════════════════════════════
# ~/.config/goose/config.yaml — Factory additions
# ═══════════════════════════════════════

# Existing Goose config fields are preserved.
# Factory-specific fields are namespaced under `factory:`.

factory:
  # API connection
  api_url: "https://api.goosefactory.dev/v1"
  ws_url: "wss://api.goosefactory.dev/v1/ws"
  
  # Auth (managed by desktop app login flow)
  # JWT stored in system keychain, not in config file
  
  # Operator preferences
  operator:
    name: "Jake"
    default_view: "decision-queue"     # "decision-queue" | "kanban" | "chat"
    keyboard_shortcuts: true
    sound_notifications: true
    desktop_notifications: true
  
  # SLA configuration
  sla:
    critical_seconds: 3600             # 1 hour
    high_seconds: 14400                # 4 hours
    medium_seconds: 86400              # 24 hours
    low_seconds: 259200                # 72 hours
    warning_threshold_percent: 25      # Warn at 25% time remaining
    
  # Escalation
  escalation:
    enabled: true
    channels: ["dashboard", "discord"]
    sms_on_breach: false
    auto_escalate_after_breach_minutes: 120
    
  # Learning system
  learning:
    enabled: true
    feedback_storage_path: "~/.config/goose/factory/feedback"
    memory_files_path: "~/.config/goose/factory/memory"
    auto_update_goosehints: true
    min_data_points_for_rules: 5
    confidence_calibration: true
    
  # Notification batching
  notifications:
    batch_interval_seconds: 300        # Batch similar notifications over 5min
    quiet_hours_start: "22:00"         # No non-critical notifications
    quiet_hours_end: "08:00"
    peak_hours: ["09:00-11:00", "14:00-16:00"]  # Prefer sending during these

  # Auto-approval (learned thresholds)
  auto_approval:
    enabled: false                     # Disabled until Level 2+ reached
    min_approval_rate: 0.90            # 90% historical approval for this task type
    min_data_points: 25
    excluded_stages: ["production"]    # Never auto-approve production

5.4 Protocol Handler Registration

// ═══════════════════════════════════════
// factory:// Protocol Handler
// Location: ui/desktop/src/main.ts
// ═══════════════════════════════════════

// Replace goose:// with factory:// protocol handler.
// Supports deep links for quick actions.

// Protocol URI patterns:

type FactoryProtocolURI =
  | `factory://approve?task_id=${string}`
  | `factory://reject?task_id=${string}`
  | `factory://pipeline?id=${string}`
  | `factory://task?id=${string}`
  | `factory://dashboard`
  | `factory://review?server_name=${string}`
  | `factory://deploy?pipeline_id=${string}&target=${"staging" | "production"}`
  ;

// Electron main process handler:
//
// app.setAsDefaultProtocolClient("factory");
//
// app.on("open-url", (event, url) => {
//   event.preventDefault();
//   const parsed = new URL(url);
//   switch (parsed.hostname) {
//     case "approve":
//       handleApproveDeepLink(parsed.searchParams.get("task_id"));
//       break;
//     case "reject":
//       handleRejectDeepLink(parsed.searchParams.get("task_id"));
//       break;
//     case "pipeline":
//       navigateToPipeline(parsed.searchParams.get("id"));
//       break;
//     case "dashboard":
//       navigateToDashboard();
//       break;
//     // ...
//   }
// });

// Deep links are used in:
// - Discord message buttons (click → opens GooseFactory with context)
// - Email notifications
// - Mobile push notifications
// - Internal cross-references

6. Learning System Contracts

6.1 Feedback Event Flow

┌──────────┐    ┌──────────┐    ┌───────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  Modal    │───▶│  McpApp  │───▶│  API      │───▶│ Validate │───▶│ Enrich   │───▶│ Store    │
│  iframe   │    │  Host    │    │ POST      │    │          │    │          │    │          │
│           │    │(postMsg) │    │/v1/feed-  │    │ Schema   │    │ NLP      │    │ JSONL    │
│           │    │          │    │ back      │    │ Dedup    │    │ Themes   │    │ Indexes  │
│           │    │          │    │           │    │ Range    │    │ Timing   │    │ Aggs     │
└──────────┘    └──────────┘    └───────────┘    └──────────┘    └──────────┘    └──────────┘
                                                                                      │
                                                                                      ▼
                                                                                ┌──────────┐
                                                                                │ Analyze  │
                                                                                │          │
                                                                                │ Patterns │
                                                                                │ Calibr.  │
                                                                                │ Rules    │
                                                                                └────┬─────┘
                                                                                     │
                                                                                     ▼
                                                                                ┌──────────┐
                                                                                │ Act      │
                                                                                │          │
                                                                                │ Memory   │
                                                                                │ .hints   │
                                                                                │ Thresholds│
                                                                                └──────────┘

6.2 Feedback Processing Pipeline Interfaces

// ═══════════════════════════════════════
// STAGE 1: Validate
// ═══════════════════════════════════════

interface ValidationResult {
  valid: boolean;
  event?: FeedbackEvent;               // Present if valid
  error?: {
    type: "schema" | "orphaned" | "range" | "duplicate";
    message: string;
    details?: unknown;
  };
}

// function validate(raw: unknown): ValidationResult

// ═══════════════════════════════════════
// STAGE 2: Enrich
// ═══════════════════════════════════════

interface EnrichedFeedbackEvent extends FeedbackEvent {
  // Added by enrichment
  _enriched: true;
  _themes?: string[];
  _sentiment?: number;                 // -1 to 1
  _actionableItems?: string[];
  _predictionDelta?: number;           // actual - predicted
  _pipelineContext?: {
    stage: PipelineStage;
    stagesCompleted: number;
    totalDuration: number;
  };
}

// function enrich(event: FeedbackEvent): EnrichedFeedbackEvent

// ═══════════════════════════════════════
// STAGE 3: Store
// ═══════════════════════════════════════

// Storage locations:
// feedback/raw/{YYYY-MM-DD}.jsonl     — Append-only daily logs
// feedback/indexes/by-server-type.json
// feedback/indexes/by-dimension.json
// feedback/indexes/by-decision.json
// feedback/indexes/by-stage.json
// feedback/aggregates/weekly/{YYYY-Www}.json
// feedback/aggregates/monthly/{YYYY-MM}.json
// feedback/aggregates/rolling-30d.json

// function store(event: EnrichedFeedbackEvent): Promise<void>

// ═══════════════════════════════════════
// STAGE 4: Analyze (batch, runs periodically)
// ═══════════════════════════════════════

interface AnalysisResults {
  // Pattern detection
  approvalPatterns: ApprovalPatterns;
  dimensionTrends: DimensionTrend[];
  
  // Theme extraction
  themes: ThemeCluster[];
  
  // Calibration
  calibration: CalibrationCurve;
  
  // Anomalies
  anomalies: Anomaly[];
  
  // Candidate rules
  candidateRules: CandidateRule[];
}

interface ApprovalPatterns {
  overall: {
    approvalRate: number;
    totalDecisions: number;
    avgTimeToDecisionMs: number;
  };
  byServerType: Record<string, {
    approved: number;
    rejected: number;
    needsWork: number;
    approvalRate: number;
  }>;
  byStage: Record<PipelineStage, {
    approvalRate: number;
    avgTimeMs: number;
    count: number;
  }>;
}

interface DimensionTrend {
  dimension: QualityDimension;
  avgScore: number;
  trend: "improving" | "stable" | "declining";
  trendDelta: number;                  // Change over period
  dataPoints: number;
  minimumThreshold?: number;           // Learned rejection threshold
}

interface ThemeCluster {
  theme: string;                       // e.g., "missing error handling"
  occurrences: number;
  avgSeverity: number;
  lastSeen: string;
  relatedDimensions: QualityDimension[];
  exampleFeedback: string[];           // Quotes from feedback
}

interface Anomaly {
  type: "approval_rate_drop" | "score_spike" | "new_rejection_pattern" | "calibration_drift";
  description: string;
  severity: "info" | "warning" | "critical";
  data: Record<string, unknown>;
  detectedAt: string;
}

interface CandidateRule {
  rule: string;                        // Human-readable rule
  promptInstruction: string;           // How to inject into system prompt
  confidence: number;                  // 0-1
  occurrences: number;
  source: "feedback_pattern" | "rejection_analysis" | "comparison" | "annotation";
  domain: QualityDimension;
  appliesTo: string[];                 // Server types or ["all"]
}

// function analyze(events: EnrichedFeedbackEvent[]): AnalysisResults

// ═══════════════════════════════════════
// STAGE 5: Act (apply learnings)
// ═══════════════════════════════════════

interface ApplyResult {
  memoryFilesUpdated: string[];
  rulesAdded: CandidateRule[];
  thresholdsChanged: Array<{
    dimension: QualityDimension;
    oldThreshold: number;
    newThreshold: number;
  }>;
  autonomyLevelChanges: Array<{
    taskType: string;
    oldLevel: number;
    newLevel: number;
  }>;
}

// function applyLearnings(analysis: AnalysisResults): Promise<ApplyResult>

6.3 Memory File Schemas

# Memory file locations:
# ~/.config/goose/factory/memory/feedback-patterns.md
# ~/.config/goose/factory/memory/quality-standards.md
# ~/.config/goose/factory/memory/jake-preferences.md
# ~/.config/goose/factory/memory/improvement-log.md
// ═══════════════════════════════════════
// feedback-patterns.md structure
// ═══════════════════════════════════════

interface FeedbackPatternsFile {
  lastUpdated: string;
  basedOnEventCount: number;
  
  sections: {
    recurringThemes: Array<{
      rank: number;
      theme: string;
      mentionCount: number;
      description: string;
    }>;
    antiPatterns: Array<{
      pattern: string;
      rejectionRate: number;             // 0-1
    }>;
    positivePatterns: Array<{
      pattern: string;
      avgScoreBonus: number;
    }>;
  };
}

// ═══════════════════════════════════════
// quality-standards.md structure
// ═══════════════════════════════════════

interface QualityStandardsFile {
  lastCalibrated: string;
  confidence: "low" | "medium" | "high";  // Based on data point count
  dataPoints: number;
  
  sections: {
    minimumScores: Array<{               // Hard gates
      dimension: QualityDimension;
      minimum: number;
      basedOn: number;                   // review count
      avgScore: number;
    }>;
    targetScores: Array<{                // What "great" looks like
      dimension: QualityDimension;
      target: number;
      topQuartile: number;
    }>;
    calibration: CalibrationCurve;
  };
}

// ═══════════════════════════════════════
// jake-preferences.md structure
// ═══════════════════════════════════════

interface JakePreferencesFile {
  lastUpdated: string;
  
  sections: {
    decisionPatterns: {
      quickApproves: string;             // What gets approved fast
      deliberatesOn: string;             // What takes time
      autoRejects: string;               // Instant rejections
    };
    communicationPreferences: {
      preferredFormats: string[];
      highEngagementTimes: string[];
      lowEngagementTimes: string[];
    };
    designTaste: Record<string, string>;
    codeStyle: Record<string, string>;
  };
}

// ═══════════════════════════════════════
// improvement-log.md structure
// ═══════════════════════════════════════

interface ImprovementLogEntry {
  date: string;
  changes: Array<{
    type: "rule_added" | "threshold_adjusted" | "calibration_updated" |
          "format_changed" | "autonomy_level_changed";
    description: string;
    source: string;                      // What triggered this change
    confidence: number;
  }>;
}

6.4 Prediction API

// ═══════════════════════════════════════
// How the learning system exposes predictions
// Used by MCP server and UI to make smart decisions
// ═══════════════════════════════════════

// GET /v1/learning/predict
interface PredictionRequest {
  workProductType: string;
  mcpServerType?: string;
  pipelineStage?: PipelineStage;
  selfAssessedScores?: DimensionScore[];
  rawConfidence?: number;              // 0-1
}

interface PredictionResponse {
  predictedScore: number;              // 1-10
  calibratedConfidence: number;        // 0-1 (adjusted from raw)
  predictedDecision: "approve" | "reject" | "needs_work";
  decisionProbabilities: {
    approve: number;
    reject: number;
    needsWork: number;
  };
  recommendedModal: string;            // Best modal type for this review
  riskFactors: string[];              // What might cause rejection
  suggestedImprovements: string[];
}

// GET /v1/learning/autonomy/:taskType
interface AutonomyLevelResponse {
  taskType: string;
  currentLevel: number;                // 0-4
  approvalRate: number;
  dataPoints: number;
  nextLevelRequirements: {
    approvalsNeeded: number;
    minApprovalRate: number;
  };
  recentRegression: boolean;
}

// GET /v1/learning/stats
interface FeedbackStats {
  totalEvents: number;
  last30Days: {
    approvalRate: number;
    avgQualityScore: number;
    avgTimeToDecisionMs: number;
    predictionAccuracy: number;         // ± delta
    autoApproveRate: number;
    feedbackVolume: number;
  };
  trends: {
    approvalRate: "improving" | "stable" | "declining";
    qualityScore: "improving" | "stable" | "declining";
    decisionSpeed: "improving" | "stable" | "declining";
  };
  activeRules: number;
  autonomyLevels: Record<string, number>;
}

6.5 Confidence Calibration Data Format

// ═══════════════════════════════════════
// Calibration maps raw AI confidence to
// actual observed approval rates.
// ═══════════════════════════════════════

interface CalibrationCurve {
  lastUpdated: string;
  totalDataPoints: number;
  
  buckets: CalibrationBucket[];
  
  // Overall calibration error (lower = better)
  expectedCalibrationError: number;    // 0-1
}

interface CalibrationBucket {
  // Bucket range
  rawConfidenceLow: number;            // e.g., 0.90
  rawConfidenceHigh: number;           // e.g., 1.00
  
  // Observed
  avgPredictedConfidence: number;
  avgActualApprovalRate: number;
  sampleCount: number;
  
  // Correction
  correctionDelta: number;             // actual - predicted (negative = overconfident)
  calibratedConfidence: number;        // What to use instead of raw
}

// Usage:
// rawConfidence = 0.92
// bucket = findBucket(rawConfidence)  → bucket for 0.90-1.00
// calibrated = rawConfidence + bucket.correctionDelta  → 0.92 + (-0.12) = 0.80
// Decision: 80% calibrated confidence → "educated guess" zone, standard review

7. Cross-Cutting Contracts

7.1 Error Handling Conventions

// ═══════════════════════════════════════
// Error Code Registry
// ═══════════════════════════════════════

// All error codes are documented. Every API response error
// uses one of the ErrorCode values from §1.3.

// Additional factory-specific error details:

interface FactoryErrorDetails {
  // For CONFLICT errors:
  currentState?: string;               // Current entity state
  requiredState?: string;              // State needed for this operation
  
  // For VALIDATION_ERROR:
  validationErrors?: Array<{
    field: string;
    message: string;
    received: unknown;
    expected: string;
  }>;
  
  // For FORBIDDEN:
  requiredScope?: Scope;
  userScopes?: Scope[];
}

// ═══════════════════════════════════════
// Retry Policy
// ═══════════════════════════════════════

interface RetryPolicy {
  // Client-side retry policy for API calls:
  maxRetries: 3;
  retryableStatusCodes: [408, 429, 500, 502, 503, 504];
  backoff: {
    type: "exponential";
    baseDelayMs: 1000;
    maxDelayMs: 30000;
    jitterMs: 500;                     // Random jitter to prevent thundering herd
  };
  
  // For 429 (Rate Limited):
  // Use Retry-After header if present
  // Otherwise use standard backoff
  
  // Non-retryable:
  // 400, 401, 403, 404, 409, 410 — fix the request, don't retry
}

// ═══════════════════════════════════════
// Idempotency
// ═══════════════════════════════════════

// Mutating endpoints support idempotency via:
// Header: Idempotency-Key: <uuid>
//
// The server stores the response for 24h.
// If the same key is sent again, the stored response is returned.
// This prevents duplicate approvals, deployments, etc.

7.2 Logging Format

// ═══════════════════════════════════════
// Structured JSON Logging
// ═══════════════════════════════════════

interface LogEntry {
  // Required fields
  timestamp: string;                   // ISO 8601
  level: "debug" | "info" | "warn" | "error" | "fatal";
  message: string;
  
  // Context
  service: "api" | "mcp-server" | "learning" | "ws" | "worker";
  requestId?: string;
  userId?: string;
  agentId?: string;
  
  // Structured data
  data?: Record<string, unknown>;
  
  // Error info
  error?: {
    name: string;
    message: string;
    stack?: string;
    code?: string;
  };
  
  // Performance
  durationMs?: number;
  
  // Tracing (OpenTelemetry)
  traceId?: string;
  spanId?: string;
}

// Example:
// {
//   "timestamp": "2025-07-14T15:30:00.000Z",
//   "level": "info",
//   "message": "Task approved",
//   "service": "api",
//   "requestId": "req_abc123",
//   "userId": "user_xyz",
//   "data": {
//     "taskId": "task_456",
//     "pipelineId": "pipe_789",
//     "decision": "approved",
//     "durationMs": 45
//   }
// }

// Log levels:
// debug  → Development only, verbose trace info
// info   → Normal operations: requests, state changes, events
// warn   → Recoverable issues: retries, degraded performance, SLA warnings
// error  → Failed operations: unhandled errors, integration failures
// fatal  → System-level failures: cannot start, data corruption

7.3 Environment Variables

# ═══════════════════════════════════════
# API Server (packages/api)
# ═══════════════════════════════════════

FACTORY_API_PORT=3100
FACTORY_API_HOST=0.0.0.0

# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/goosefactory
DATABASE_POOL_SIZE=20

# Redis
REDIS_URL=redis://localhost:6379

# Auth
JWT_SECRET=<256-bit secret>
JWT_EXPIRY_SECONDS=900          # 15 minutes
REFRESH_TOKEN_EXPIRY_SECONDS=604800  # 7 days

# Storage
S3_ENDPOINT=https://your-r2.r2.cloudflarestorage.com
S3_BUCKET=goosefactory-assets
S3_ACCESS_KEY_ID=<key>
S3_SECRET_ACCESS_KEY=<secret>

# External integrations
DISCORD_BOT_TOKEN=<token>
DISCORD_GUILD_ID=<guild_id>
DISCORD_TASKS_CHANNEL_ID=<channel_id>
GITHUB_APP_ID=<app_id>
GITHUB_PRIVATE_KEY=<base64-encoded>

# Observability
LOG_LEVEL=info                  # debug | info | warn | error
OTEL_EXPORTER_ENDPOINT=https://otel.example.com
OTEL_SERVICE_NAME=goosefactory-api

# ═══════════════════════════════════════
# MCP Server (packages/mcp-server)
# ═══════════════════════════════════════

FACTORY_MCP_TRANSPORT=stdio     # stdio | sse
FACTORY_API_URL=http://localhost:3100/v1
FACTORY_API_KEY=<internal-api-key>

# ═══════════════════════════════════════
# Learning System (packages/learning)
# ═══════════════════════════════════════

FEEDBACK_STORAGE_PATH=~/.config/goose/factory/feedback
MEMORY_FILES_PATH=~/.config/goose/factory/memory
ANALYSIS_INTERVAL_MINUTES=60    # Run analysis every hour
MIN_DATA_POINTS_FOR_RULES=5
CONFIDENCE_CALIBRATION_ENABLED=true

# ═══════════════════════════════════════
# Desktop App (packages/desktop)
# ═══════════════════════════════════════

FACTORY_API_URL=https://api.goosefactory.dev/v1
FACTORY_WS_URL=wss://api.goosefactory.dev/v1/ws
# GOOSE_PROVIDER, GOOSE_MODEL etc. are inherited from Goose

7.4 Monorepo File/Folder Structure

goosefactory/
├── CONTRACTS.md                       # ← THIS FILE (the source of truth)
├── README.md                          # Project overview
├── package.json                       # Monorepo root (workspaces)
├── turbo.json                         # Turborepo config (or nx.json)
├── tsconfig.base.json                 # Shared TypeScript config
├── .env.example                       # Template env vars
├── .gitignore
│
├── packages/
│   ├── shared/                        # ← SHARED TYPES (generated from CONTRACTS.md)
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   │       ├── types/
│   │       │   ├── pipeline.ts        # Pipeline, PipelineStage, etc.
│   │       │   ├── task.ts            # Task, TaskStatus, etc.
│   │       │   ├── approval.ts        # Approval, ApprovalType, etc.
│   │       │   ├── agent.ts           # Agent, AgentHealth, etc.
│   │       │   ├── asset.ts           # Asset, AssetType, etc.
│   │       │   ├── feedback.ts        # FeedbackEvent, FeedbackPayload, etc.
│   │       │   ├── modal.ts           # ModalMessage, ModalResponse, etc.
│   │       │   ├── api.ts             # ApiResponse, ApiError, PaginationParams
│   │       │   ├── ws.ts              # WsEvent, WsChannel, event payloads
│   │       │   └── index.ts           # Re-exports everything
│   │       ├── constants/
│   │       │   ├── stages.ts          # KANBAN_STAGES, stage order
│   │       │   ├── errors.ts          # ErrorCode enum values
│   │       │   └── sla.ts             # Default SLA values
│   │       └── schemas/               # Zod schemas for runtime validation
│   │           ├── feedback.schema.ts
│   │           ├── pipeline.schema.ts
│   │           └── task.schema.ts
│   │
│   ├── api/                           # API Engineer's domain
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── Dockerfile
│   │   └── src/
│   │       ├── server.ts
│   │       ├── routes/
│   │       ├── services/
│   │       ├── ws/
│   │       ├── middleware/
│   │       └── db/
│   │
│   ├── mcp-server/                    # MCP Engineer's domain
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   │       ├── index.ts
│   │       ├── tools/
│   │       ├── resources/
│   │       ├── prompts/
│   │       └── apps/
│   │
│   ├── modals/                        # Modal Designer's domain
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   │       ├── modals/                # 25 HTML modal files
│   │       │   ├── traffic-light.html
│   │       │   ├── tinder-swipe.html
│   │       │   └── ...
│   │       ├── shared/                # Common CSS/JS
│   │       │   ├── base-styles.css
│   │       │   ├── submit-handler.js
│   │       │   ├── timing-tracker.js
│   │       │   └── celebration-effects.js
│   │       └── registry.ts            # Modal registrations
│   │
│   ├── learning/                      # Learning Engineer's domain
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   │       ├── pipeline/
│   │       ├── memory/
│   │       ├── calibration/
│   │       └── rules/
│   │
│   └── desktop/                       # UI Engineer's domain (Goose fork)
│       ├── ... (forked Goose repo)
│       └── ui/desktop/src/
│           └── components/factory/    # Factory-specific UI components
│
└── infra/
    ├── db/
    │   ├── migrations/
    │   └── seed/
    └── docker/
        ├── docker-compose.yml
        ├── docker-compose.dev.yml
        ├── Dockerfile.api
        └── Dockerfile.learning

7.5 Shared Package Convention

// ═══════════════════════════════════════
// packages/shared is the SINGLE source of
// TypeScript types. All other packages import from it.
// ═══════════════════════════════════════

// In each package's package.json:
// "dependencies": {
//   "@goosefactory/shared": "workspace:*"
// }

// Import convention:
// import type { Pipeline, Task, FeedbackEvent } from "@goosefactory/shared";
// import { KANBAN_STAGES, DEFAULT_SLA } from "@goosefactory/shared/constants";
// import { feedbackSchema } from "@goosefactory/shared/schemas";

// RULE: If you need a new type that crosses package boundaries,
// add it to @goosefactory/shared first, then use it.
// Never define the same type in two packages.

7.6 API Versioning & Deprecation

// ═══════════════════════════════════════
// Versioning rules
// ═══════════════════════════════════════

// 1. URL path versioning: /v1/, /v2/, etc.
// 2. Minor changes (new optional fields, new endpoints) → same version
// 3. Breaking changes (removed fields, changed types) → new version
// 4. Deprecated versions get a Sunset header with 90-day notice
// 5. Only 2 versions supported at any time (current + previous)

// Response headers on deprecated endpoints:
// Sunset: Sat, 14 Oct 2025 00:00:00 GMT
// Deprecation: true
// Link: <https://api.goosefactory.dev/v2/pipelines>; rel="successor-version"

7.7 Event Bus Contract

// ═══════════════════════════════════════
// Redis Streams event bus
// Used for decoupled async processing
// ═══════════════════════════════════════

// Stream names:
const REDIS_STREAMS = {
  pipelines: "factory.pipelines",
  tasks: "factory.tasks",
  agents: "factory.agents",
  deploys: "factory.deploys",
  notifications: "factory.notifications",
  feedback: "factory.feedback",
} as const;

// Consumer groups (each independently processes events):
const CONSUMER_GROUPS = {
  wsBroadcaster: "ws-broadcaster",       // Fans out to WebSocket clients
  notificationWorker: "notification-worker",
  discordBridge: "discord-bridge",
  slaMonitor: "sla-monitor",
  auditWriter: "audit-writer",
  mcpResourceUpdater: "mcp-resource-updater",
  feedbackProcessor: "feedback-processor",
} as const;

// Event format in Redis Stream:
interface RedisStreamEvent {
  id: string;                          // Redis stream ID (timestamp-sequence)
  type: WebSocketEventType;
  timestamp: string;
  data: string;                        // JSON-serialized payload
  source: string;                      // Which service produced this
}

// Each consumer group processes events independently.
// Consumer group acknowledges events after processing (exactly-once within group).
// Events are retained for 7 days, then trimmed.

Appendix A: Stage Transition Matrix

FROM → TO              | Trigger                | Creates Task? | Validation
─────────────────────────────────────────────────────────────────────────────
intake → scaffolding   | Pipeline created       | No            | None
scaffolding → building | Scaffold complete      | No            | Assets exist
building → testing     | Build succeeds         | No            | Build artifact exists
testing → review       | Tests pass             | YES (review)  | Coverage ≥ threshold
review → staging       | Approval approved      | YES (deploy)  | Code review approved
staging → production   | Staging tests pass     | YES (deploy)  | Staging deploy healthy
production → published | Production deploy OK   | No            | Deploy health check
ANY → failed           | Unrecoverable error    | YES (fix)     | None
failed → {previous}    | Manual retry           | YES (approval)| None

Appendix B: SLA Escalation Timeline

T+0min    Task created      → Dashboard notification + Discord embed
T+30min   Reminder #1       → Discord DM + dashboard badge pulse
T+2h      Reminder #2       → Discord @mention + push notification
T+SLA-25% SLA Warning       → Discord @here + sound alert in app
T+SLA     SLA Breach        → All channels + SMS (if configured)
T+SLA+2h  Critical          → Phone + auto-escalate to admin

Appendix C: Modal Type → Use Case Mapping

Modal Type          | Use Case              | Feedback Types Captured
────────────────────────────────────────────────────────────────────
traffic-light       | binary_decision       | decision, tags
tinder-swipe        | batch_review          | batchDecisions
report-card         | multi_dimensional     | scores, freeText
thermometer         | subjective            | temperature
spotlight           | annotation            | annotations
ranking-arena       | comparison            | ranking
speed-round         | batch_review          | batchDecisions
emoji-scale         | subjective            | decision (sentiment)
before-after        | comparison            | comparison
priority-poker      | estimation            | estimation
mission-briefing    | high_stakes           | decision, checklist, confidence
judges-scorecard    | multi_dimensional     | scores
quick-pulse         | subjective            | temperature (quick)
decision-tree       | binary_decision       | decision (branching)
hot-take            | binary_decision       | decision (timed)
side-by-side        | comparison            | comparison
checklist-ceremony  | verification          | checklist
confidence-meter    | (paired w/ any)       | confidence
voice-of-customer   | subjective            | scores, freeText
retrospective       | retrospective         | retrospective
slot-machine        | subjective            | scores (gamified)
mood-ring           | subjective            | custom (color/mood)
war-room            | triage                | batchDecisions
applause-meter      | subjective            | custom (tap count)
crystal-ball        | estimation            | estimation, custom

This document is the single source of truth for all GooseFactory inter-agent contracts. Every type defined here is canonical. If implementation differs from this document, the implementation is wrong.