@mastra/factory 0.12.0-alpha.13 → 0.12.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/factory.d.ts +8 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +4 -3
  4. package/dist/factory.js.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/integrations/github/acceptance-labels.d.ts +17 -0
  8. package/dist/integrations/github/acceptance-labels.d.ts.map +1 -0
  9. package/dist/integrations/github/acceptance-labels.js +55 -0
  10. package/dist/integrations/github/acceptance-labels.js.map +1 -0
  11. package/dist/integrations/github/sandbox.d.ts +10 -13
  12. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  13. package/dist/integrations/github/sandbox.js +74 -65
  14. package/dist/integrations/github/sandbox.js.map +1 -1
  15. package/dist/packages/_internals/workspace/dist/index.js +11 -1
  16. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  17. package/dist/routes/work-items.d.ts.map +1 -1
  18. package/dist/routes/work-items.js +7 -0
  19. package/dist/routes/work-items.js.map +1 -1
  20. package/dist/rules/transition-service.d.ts +12 -1
  21. package/dist/rules/transition-service.d.ts.map +1 -1
  22. package/dist/rules/transition-service.js +28 -2
  23. package/dist/rules/transition-service.js.map +1 -1
  24. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  25. package/dist/sandbox/session-sandbox.js +4 -1
  26. package/dist/sandbox/session-sandbox.js.map +1 -1
  27. package/dist/storage/domains/work-items/base.d.ts +8 -0
  28. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  29. package/dist/storage/domains/work-items/base.js +10 -1
  30. package/dist/storage/domains/work-items/base.js.map +1 -1
  31. package/dist/workspace.d.ts +8 -0
  32. package/dist/workspace.d.ts.map +1 -1
  33. package/dist/workspace.js +16 -2
  34. package/dist/workspace.js.map +1 -1
  35. package/package.json +8 -8
  36. package/dist/session/checkpoint-capture.d.ts +0 -18
  37. package/dist/session/checkpoint-capture.d.ts.map +0 -1
  38. package/dist/session/checkpoint-capture.js +0 -27
  39. package/dist/session/checkpoint-capture.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"work-items.js","names":["#resolveTenant","#resolveProject","#auditWorkItemPatch","#proposalRoute"],"sources":["../../src/routes/work-items.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for Factory work items (the kanban board).\n *\n * Registered alongside the other `/web/*` routes behind the host auth gate.\n * The board is org-wide: every route re-resolves the caller's `(orgId, userId)`\n * tenant and scopes reads/writes by `orgId`, so any org member sees and moves\n * the same cards while `created_by` / stage history record who acted.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type {\n FactoryStartCoordinator,\n FactoryStartPreparedResult,\n FactoryStartRequest,\n} from '../rules/start-coordinator.js';\nimport { FactoryStartTransitionError } from '../rules/start-coordinator.js';\nimport { roleForStage } from '../rules/transition-service.js';\nimport type { FactoryTransitionRequest, FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRuleBoard } from '../rules/types.js';\nimport { FACTORY_RULE_BOARDS, isFactoryRuleStage } from '../rules/types.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport { thresholdsOrDefault } from '../storage/domains/queue-health/base.js';\nimport type {\n CreateWorkItemInput,\n ExternalWorkItemSource,\n FactoryDeferredDecisionRecord,\n FactoryDispatchStatus,\n UpdateWorkItemInput,\n WorkItemPriorState,\n WorkItemRow,\n WorkItemSessionInput,\n WorkItemStage,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n FACTORY_PULL_REQUEST_RECONCILIATION_KEY,\n FACTORY_RULE_MATERIALIZATION_KEY,\n WorkItemRelationError,\n} from '../storage/domains/work-items/base.js';\nimport { computeFactoryMetrics, parseMetricsRange } from '../storage/domains/work-items/metrics.js';\nimport { buildAttentionRoutes, factoryDecisionType } from './attention.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface WorkItemRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Factory projects domain — validates the `:id` project belongs to the caller's org. */\n projects: FactoryProjectsStorage;\n /** Work-items domain backing the kanban board. */\n workItems: WorkItemsStorage;\n /** Comments domain — backs the mention attention provider. */\n comments: WorkItemCommentsStorage;\n /** Per-project queue-health threshold config. */\n queueHealth: QueueHealthStorage;\n /** Governed stage-transition service. Stage moves 503 when absent. */\n transitionService?: Pick<FactoryTransitionService, 'transition' | 'ruleSetVersion'>;\n /** Coordinator that binds a Factory run before dispatching its kickoff. */\n startCoordinator?: Pick<FactoryStartCoordinator, 'prepare'>;\n /** Materialized sessions, read to report which of the listed cards are being worked. */\n liveSessions: Pick<LiveSessions, 'isRunning'>;\n}\n\n/** The card as clients see it, without the dispatcher's internal bookkeeping. */\nfunction toWireWorkItem(item: WorkItemRow): WorkItemRow {\n if (\n !item.metadata ||\n (!(FACTORY_RULE_MATERIALIZATION_KEY in item.metadata) &&\n !(FACTORY_PULL_REQUEST_RECONCILIATION_KEY in item.metadata))\n ) {\n return item;\n }\n return { ...item, metadata: publicWorkItemMetadata(item.metadata) ?? {} };\n}\n\n/** Session ids of the listed cards whose agent run is in flight. */\nfunction runningSessionIds(items: WorkItemRow[], liveSessions: Pick<LiveSessions, 'isRunning'>): string[] {\n const running = new Set<string>();\n for (const item of items) {\n for (const { sessionId } of Object.values(item.sessions)) {\n if (liveSessions.isRunning(sessionId)) running.add(sessionId);\n }\n }\n return [...running];\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nconst MAX_STAGES = 16;\nconst MAX_STAGE_LENGTH = 64;\nconst MAX_METADATA_BYTES = 16 * 1024;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction validStages(value: unknown): value is WorkItemStage[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.length <= MAX_STAGES &&\n value.every(\n stage => typeof stage === 'string' && stage.length <= MAX_STAGE_LENGTH && /^[a-z0-9][a-z0-9_-]*$/i.test(stage),\n ) &&\n new Set(value).size === value.length\n );\n}\n\nfunction validMetadata(value: unknown): value is Record<string, unknown> | null {\n if (value === null) return true;\n if (!isRecord(value)) return false;\n try {\n return JSON.stringify(value).length <= MAX_METADATA_BYTES;\n } catch {\n return false;\n }\n}\n\nfunction publicWorkItemMetadata(value: Record<string, unknown> | null): Record<string, unknown> | null {\n if (value === null) return null;\n const {\n [FACTORY_RULE_MATERIALIZATION_KEY]: _materialization,\n [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: _reconciliation,\n ...metadata\n } = value;\n return metadata;\n}\n\nfunction parseExternalSource(value: unknown): ExternalWorkItemSource | null | undefined {\n if (value === undefined || value === null) return value;\n if (!isRecord(value)) return undefined;\n const { integrationId, type, externalId, url } = value;\n if (typeof integrationId !== 'string' || integrationId.length === 0 || integrationId.length > 128) return undefined;\n if (typeof type !== 'string' || type.length === 0 || type.length > 128) return undefined;\n if (typeof externalId !== 'string' || externalId.length === 0 || externalId.length > 512) return undefined;\n if (url !== undefined && (typeof url !== 'string' || url.length > 2048)) return undefined;\n return { integrationId, type, externalId, ...(url !== undefined ? { url } : {}) };\n}\n\nfunction parseParentWorkItemId(value: unknown): string | null | undefined {\n if (value === null) return null;\n if (typeof value !== 'string' || !UUID_RE.test(value)) return undefined;\n return value;\n}\n\nfunction parseSessions(value: unknown): Record<string, WorkItemSessionInput> | undefined {\n if (!isRecord(value)) return undefined;\n const out: Record<string, WorkItemSessionInput> = {};\n for (const [role, session] of Object.entries(value)) {\n if (!role || role.length > 64 || !isRecord(session)) return undefined;\n const { sessionId, branch, threadId } = session;\n if (typeof sessionId !== 'string' || sessionId.length === 0 || sessionId.length > 512) return undefined;\n if (typeof branch !== 'string' || branch.length === 0 || branch.length > 512) return undefined;\n if (typeof threadId !== 'string' || threadId.length === 0 || threadId.length > 512) return undefined;\n out[role] = { sessionId, branch, threadId };\n }\n return out;\n}\n\n/** Validate an untrusted create body. Unknown keys are dropped. */\nexport function parseCreateWorkItem(body: unknown): CreateWorkItemInput | null {\n if (!isRecord(body)) return null;\n const { externalSource, title, stages, sessions, metadata } = body;\n if (typeof title !== 'string' || title.trim().length === 0 || title.length > 500) return null;\n\n const hasParentWorkItemId = 'parentWorkItemId' in body;\n const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : undefined;\n if (hasParentWorkItemId && parentWorkItemId === undefined) return null;\n const parsedSource = parseExternalSource(externalSource);\n if (externalSource !== undefined && parsedSource === undefined) return null;\n if (stages !== undefined && !validStages(stages)) return null;\n const parsedSessions = sessions === undefined ? undefined : parseSessions(sessions);\n if (sessions !== undefined && parsedSessions === undefined) return null;\n let parsedMetadata: Record<string, unknown> | null | undefined;\n if (metadata !== undefined) {\n if (!validMetadata(metadata)) return null;\n parsedMetadata = publicWorkItemMetadata(metadata);\n }\n\n return {\n title: title.trim(),\n ...(parsedSource !== undefined ? { externalSource: parsedSource } : {}),\n ...(hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {}),\n ...(stages !== undefined ? { stages } : {}),\n ...(parsedSessions !== undefined ? { sessions: parsedSessions } : {}),\n ...(parsedMetadata !== undefined ? { metadata: parsedMetadata } : {}),\n };\n}\n\n/** Validate an untrusted patch body. Unknown keys are dropped. */\nexport function parseUpdateWorkItem(body: unknown): UpdateWorkItemInput | null {\n if (!isRecord(body)) return null;\n const { title, stages, sessions, metadata } = body;\n const hasParentWorkItemId = 'parentWorkItemId' in body;\n if (\n title === undefined &&\n stages === undefined &&\n sessions === undefined &&\n metadata === undefined &&\n !hasParentWorkItemId\n )\n return null;\n const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : undefined;\n if (hasParentWorkItemId && parentWorkItemId === undefined) return null;\n if (title !== undefined && (typeof title !== 'string' || title.trim().length === 0 || title.length > 500))\n return null;\n if (stages !== undefined && !validStages(stages)) return null;\n const parsedSessions = sessions === undefined ? undefined : parseSessions(sessions);\n if (sessions !== undefined && parsedSessions === undefined) return null;\n let parsedMetadata: Record<string, unknown> | null | undefined;\n if (metadata !== undefined) {\n if (!validMetadata(metadata)) return null;\n parsedMetadata = publicWorkItemMetadata(metadata);\n }\n\n return {\n ...(hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {}),\n ...(title !== undefined ? { title: title.trim() } : {}),\n ...(stages !== undefined ? { stages } : {}),\n ...(parsedSessions !== undefined ? { sessions: parsedSessions } : {}),\n ...(parsedMetadata !== undefined ? { metadata: parsedMetadata } : {}),\n };\n}\n\nasync function readJson(c: Context): Promise<unknown | undefined> {\n try {\n return await c.req.json();\n } catch {\n return undefined;\n }\n}\n\n/** Fields a PATCH touched, for the bounded `updated` event summary. */\nfunction patchedFields(patch: Record<string, unknown>): string[] {\n return Object.keys(patch).filter(key => patch[key] !== undefined);\n}\n\nfunction boundedText(value: unknown, max: number): string | undefined {\n if (typeof value !== 'string') return undefined;\n const normalized = value.trim();\n return normalized.length > 0 && normalized.length <= max ? normalized : undefined;\n}\n\nfunction parseTransitionBody(\n body: unknown,\n): Omit<FactoryTransitionRequest, 'orgId' | 'factoryProjectId' | 'workItemId' | 'actor'> | null {\n if (!isRecord(body)) return null;\n const board = FACTORY_RULE_BOARDS.includes(body.board as FactoryRuleBoard)\n ? (body.board as FactoryRuleBoard)\n : undefined;\n const stage = isFactoryRuleStage(body.stage) ? body.stage : undefined;\n const requestId = boundedText(body.requestId, 256);\n const cause = boundedText(body.cause, 256);\n if (\n !board ||\n !stage ||\n !requestId ||\n !UUID_RE.test(requestId) ||\n !cause ||\n !Number.isInteger(body.expectedRevision) ||\n Number(body.expectedRevision) < 1\n ) {\n return null;\n }\n return {\n board,\n stage,\n expectedRevision: Number(body.expectedRevision),\n ingress: { type: 'human', identity: requestId },\n cause,\n };\n}\n\nfunction parseInvocation(value: unknown): FactoryStartRequest['invocation'] | undefined | null {\n if (value === undefined) return undefined;\n if (!isRecord(value)) return null;\n if (value.type === 'prompt') {\n const prompt = boundedText(value.prompt, 16_384);\n return prompt ? { type: 'prompt', prompt } : null;\n }\n if (value.type === 'skill') {\n const skillName = boundedText(value.skillName, 64);\n const args = typeof value.arguments === 'string' && value.arguments.length <= 16_384 ? value.arguments : undefined;\n return skillName && args !== undefined ? { type: 'skill', skillName, arguments: args } : null;\n }\n return null;\n}\n\nfunction parseStartBody(\n body: unknown,\n tenant: { orgId: string; userId: string },\n factoryProjectId: string,\n): FactoryStartRequest | null {\n if (!isRecord(body) || !isRecord(body.workItem)) return null;\n const input = parseCreateWorkItem(body.workItem.input);\n const sessionId = boundedText(body.sessionId, 256);\n const threadTitle = boundedText(body.threadTitle, 512);\n const kickoffKey = boundedText(body.kickoffKey, 256);\n const invocation = parseInvocation(body.invocation);\n const destinationStage = isFactoryRuleStage(body.destinationStage) ? body.destinationStage : undefined;\n const role = boundedText(body.workItem.role, 32);\n const id = body.workItem.id === undefined ? undefined : boundedText(body.workItem.id, 64);\n if (body.workItem.id !== undefined && (!id || !UUID_RE.test(id))) return null;\n if (\n !input ||\n !sessionId ||\n !UUID_RE.test(sessionId) ||\n !threadTitle ||\n !kickoffKey ||\n !UUID_RE.test(kickoffKey) ||\n invocation === null ||\n !destinationStage ||\n !role\n ) {\n return null;\n }\n const threadTags = isRecord(body.threadTags)\n ? Object.fromEntries(\n Object.entries(body.threadTags)\n .filter(\n (entry): entry is [string, string] =>\n boundedText(entry[0], 64) !== undefined && boundedText(entry[1], 256) !== undefined,\n )\n .map(([key, value]) => [key, value.trim()]),\n )\n : undefined;\n return {\n ...tenant,\n factoryProjectId,\n sessionId,\n threadTitle,\n threadTags,\n kickoffKey,\n preapprovePlans: body.preapprovePlans === true,\n invocation,\n destinationStage,\n workItem: { id, role, input },\n };\n}\n\nconst DECISION_STATUSES = new Set<FactoryDispatchStatus>([\n 'pending',\n 'proposed',\n 'dismissed',\n 'superseded',\n 'leased',\n 'retry',\n 'succeeded',\n 'failed',\n]);\nconst DEFAULT_DECISION_PAGE_SIZE = 25;\nconst MAX_DECISION_PAGE_SIZE = 50;\n\nfunction parseDecisionStatuses(raw: string | undefined): FactoryDispatchStatus[] | undefined {\n if (!raw) return undefined;\n const statuses = [...new Set(raw.split(',').map(status => status.trim()))].filter(\n (status): status is FactoryDispatchStatus => DECISION_STATUSES.has(status as FactoryDispatchStatus),\n );\n return statuses.length > 0 ? statuses : undefined;\n}\n\nfunction parseDecisionLimit(raw: string | undefined): number {\n const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_DECISION_PAGE_SIZE;\n if (!Number.isFinite(parsed)) return DEFAULT_DECISION_PAGE_SIZE;\n return Math.max(1, Math.min(MAX_DECISION_PAGE_SIZE, parsed));\n}\n\nfunction encodeDecisionCursor(decision: FactoryDeferredDecisionRecord): string {\n return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), 'utf8').toString('base64url');\n}\n\nfunction parseDecisionCursor(raw: string | undefined): { createdAt: Date; id: string } | undefined {\n if (!raw) return undefined;\n try {\n const decoded = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8')) as unknown;\n if (\n !Array.isArray(decoded) ||\n decoded.length !== 2 ||\n typeof decoded[0] !== 'string' ||\n typeof decoded[1] !== 'string'\n ) {\n return undefined;\n }\n const createdAt = new Date(decoded[0]);\n if (Number.isNaN(createdAt.getTime()) || !UUID_RE.test(decoded[1])) return undefined;\n return { createdAt, id: decoded[1] };\n } catch {\n return undefined;\n }\n}\n\n/** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */\nfunction summaryRole(decision: Record<string, unknown>): string | null {\n if (typeof decision.role === 'string') return decision.role.slice(0, 32);\n if (decision.type !== 'transition') return null;\n const board = decision.board;\n const stage = decision.stage;\n if ((board !== 'work' && board !== 'review') || !isFactoryRuleStage(stage)) return null;\n return roleForStage(board, stage);\n}\n\nfunction decisionSummary(decision: FactoryDeferredDecisionRecord) {\n return {\n id: decision.id,\n evaluationId: decision.evaluationId,\n workItemId: decision.workItemId,\n type: factoryDecisionType(decision),\n role: summaryRole(decision.decision),\n status: decision.status,\n attempts: decision.attempts,\n failureOccurrence: decision.failureOccurrence,\n failureCode: decision.failureCode,\n canRetry: factoryDispatchFailureMetadata(decision.failureCode).canRetry,\n lastError: decision.lastError?.slice(0, 512) ?? null,\n createdAt: decision.createdAt.toISOString(),\n updatedAt: decision.updatedAt.toISOString(),\n completedAt: decision.completedAt?.toISOString() ?? null,\n };\n}\n\nexport class WorkItemRoutes extends Route<WorkItemRoutesDeps> {\n /** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'The Factory board requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n /**\n * Resolve the tenant AND the org-owned project from the `:id` param. Work\n * items hang off a project, so listing/creating requires the project to\n * exist in the caller's org.\n */\n async #resolveProject(\n c: Context,\n ): Promise<\n { orgId: string; userId: string; factoryProjectId: string; defaultModelId: string | null } | { response: Response }\n > {\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const { projects } = this.deps;\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n return { ...tenant, factoryProjectId: projectId, defaultModelId: project.defaultModelId };\n }\n\n /**\n * Emit the audit events a successful work-item PATCH implies: always\n * `updated`, plus `stage_moved` when the stages actually changed and one\n * `run.started` per session role the patch introduced.\n */\n async #auditWorkItemPatch({\n context,\n item,\n previous,\n patch,\n }: {\n context: Context;\n item: WorkItemRow;\n previous: WorkItemPriorState;\n patch: Record<string, unknown>;\n }): Promise<void> {\n const { audit } = this.deps;\n const target = { type: 'work_item', id: item.id, name: item.title };\n await audit.emit({\n context,\n input: {\n action: 'factory.work_item.updated',\n factoryProjectId: item.factoryProjectId,\n targets: [target],\n metadata: { fields: patchedFields(patch) },\n },\n });\n\n const stagesChanged =\n patch.stages !== undefined &&\n (previous.stages.length !== item.stages.length || previous.stages.some((s, i) => s !== item.stages[i]));\n if (stagesChanged) {\n await audit.emit({\n context,\n input: {\n action: 'factory.work_item.stage_moved',\n factoryProjectId: item.factoryProjectId,\n targets: [target],\n metadata: { from: previous.stages, to: item.stages },\n },\n });\n }\n\n const newRoles = Object.keys(item.sessions).filter(role => !previous.sessionRoles.includes(role));\n for (const role of newRoles) {\n const session = item.sessions[role];\n await audit.emit({\n context,\n input: {\n action: 'factory.run.started',\n factoryProjectId: item.factoryProjectId,\n targets: [target],\n metadata: {\n role,\n branch: session?.branch,\n threadId: session?.threadId,\n sessionId: session?.sessionId,\n },\n },\n });\n }\n }\n\n /** Releasing a parked run and dropping it: same request, opposite outcomes, both audited as consent. */\n #proposalRoute({\n verb,\n settle,\n }: {\n verb: 'approve' | 'dismiss';\n settle: (\n orgId: string,\n factoryProjectId: string,\n decisionId: string,\n now: Date,\n userId: string,\n ) => Promise<FactoryDeferredDecisionRecord | null>;\n }): ApiRoute {\n const { audit, workItems } = this.deps;\n return registerApiRoute(`/web/factory/projects/:id/decisions/:decisionId/${verb}`, {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const decisionId = context.req.param('decisionId');\n if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: 'invalid_decision_id' }, 422);\n await workItems.ensureReady();\n const now = new Date();\n const decision = await settle(resolved.orgId, resolved.factoryProjectId, decisionId, now, resolved.userId);\n if (!decision) return c.json({ error: 'decision_not_proposed' }, 409);\n // Releasing a proposal is a person taking the item on. Approval arms the\n // item's autonomy inside the same storage transaction (see\n // approveDeferredDecision), so follow-up runs no longer wait for approval.\n await audit.emit({\n context,\n input: {\n action: verb === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n factoryProjectId: resolved.factoryProjectId,\n targets: decision.workItemId\n ? [{ type: 'work_item', id: decision.workItemId }]\n : [{ type: 'rule_decision', id: decision.id }],\n metadata: { decisionId: decision.id, effect: factoryDecisionType(decision) },\n },\n });\n return c.json({ decision: decisionSummary(decision) });\n },\n });\n }\n\n /** Build the Factory work-item routes as Mastra `apiRoutes`. */\n routes(): ApiRoute[] {\n const { audit, workItems, queueHealth, transitionService, startCoordinator, liveSessions } = this.deps;\n return [\n // ── List the org's work items for a project, and which are being worked ─\n registerApiRoute('/web/factory/projects/:id/work-items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n await workItems.ensureReady();\n const items = await workItems.list({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n });\n return c.json({\n workItems: items.map(toWireWorkItem),\n runningSessionIds: runningSessionIds(items, liveSessions),\n });\n },\n }),\n\n // ── Flow metrics aggregated over the project's work items ───────────────\n registerApiRoute('/web/factory/projects/:id/metrics', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n const { windowStart, windowEnd } = parseMetricsRange(\n loose(c).req.query('from'),\n loose(c).req.query('to'),\n new Date(),\n );\n await workItems.ensureReady();\n const items = await workItems.list({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n });\n return c.json({ metrics: computeFactoryMetrics(items, { windowStart, windowEnd }) });\n },\n }),\n\n // ── Per-project queue-health age-threshold config (seconds) ─────────────\n registerApiRoute('/web/factory/projects/:id/health/thresholds', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n await queueHealth.ensureReady();\n const stored = await queueHealth.getConfig(resolved.orgId, resolved.factoryProjectId);\n // Validate at the read choke point: `getConfig` round-trips a stored\n // JSONB row, and only `saveConfig` validates on write — a corrupted or\n // hand-edited row (empty / non-ascending) would otherwise flow to the\n // chart and invert bucket colors. Fall back to the default on invalid.\n return c.json({ thresholds: thresholdsOrDefault(stored) });\n },\n }),\n\n // ── Bounded durable rule-decision status ────────────────────────────────\n registerApiRoute('/web/factory/projects/:id/decisions', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n\n const cursorRaw = context.req.query('before');\n const before = parseDecisionCursor(cursorRaw);\n if (cursorRaw && !before) return c.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n const page = await workItems.listDeferredDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: parseDecisionStatuses(context.req.query('statuses')),\n before,\n limit: parseDecisionLimit(context.req.query('limit')),\n });\n const last = page.decisions.at(-1);\n return c.json({\n decisions: page.decisions.map(decisionSummary),\n ...(page.hasMore && last ? { nextCursor: encodeDecisionCursor(last) } : {}),\n });\n },\n }),\n\n ...buildAttentionRoutes({\n workItems,\n comments: this.deps.comments,\n resolveProject: context => this.#resolveProject(loose(context)),\n }),\n\n this.#proposalRoute({ verb: 'approve', settle: workItems.approveDeferredDecision.bind(workItems) }),\n this.#proposalRoute({ verb: 'dismiss', settle: workItems.dismissDeferredDecision.bind(workItems) }),\n\n registerApiRoute('/web/factory/projects/:id/decisions/:decisionId/retry', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const decisionId = context.req.param('decisionId');\n if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: 'invalid_decision_id' }, 422);\n await workItems.ensureReady();\n const current = await workItems.getDeferredDecision(resolved.orgId, resolved.factoryProjectId, decisionId);\n if (\n !current ||\n current.status !== 'failed' ||\n !factoryDispatchFailureMetadata(current.failureCode).canRetry\n ) {\n return c.json({ error: 'decision_not_retryable' }, 409);\n }\n const decision = await workItems.retryDeferredDecision(\n resolved.orgId,\n resolved.factoryProjectId,\n decisionId,\n new Date(),\n );\n if (!decision) return c.json({ error: 'decision_not_retryable' }, 409);\n return c.json({ decision: decisionSummary(decision) });\n },\n }),\n\n // ── Create (upsert on sourceKey) a work item ─────────────────────────────\n registerApiRoute('/web/factory/projects/:id/work-items', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n\n const body = await readJson(loose(c));\n if (body === undefined) return c.json({ error: 'Invalid JSON body' }, 400);\n const input = parseCreateWorkItem(body);\n if (!input) return c.json({ error: 'invalid_work_item' }, 400);\n if ((input.stages ?? ['intake']).length !== 1 || (input.stages ?? ['intake'])[0] !== 'intake') {\n return c.json(\n { error: 'governed_transition_required', message: 'New work items must enter through Factory intake.' },\n 409,\n );\n }\n\n await workItems.ensureReady();\n try {\n const result = await workItems.upsert({\n orgId: resolved.orgId,\n userId: resolved.userId,\n factoryProjectId: resolved.factoryProjectId,\n input,\n reuseMode: 'non-stage',\n });\n let item = result.item;\n if (result.created) {\n if (!transitionService) {\n await workItems.delete({ orgId: resolved.orgId, id: item.id });\n return c.json({ error: 'factory_transitions_unavailable' }, 503);\n }\n const entered = await transitionService.transition({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n workItemId: item.id,\n board: item.externalSource?.type === 'pull-request' ? 'review' : 'work',\n stage: 'intake',\n expectedRevision: item.revision,\n actor: { type: 'human', id: resolved.userId },\n ingress: { type: 'human', identity: `work-item:${item.id}:initial-entry` },\n cause: 'work_item_created',\n initialEntry: true,\n });\n if (entered.status === 'rejected') {\n await workItems.delete({ orgId: resolved.orgId, id: item.id });\n return c.json({ status: 'rejected', code: entered.code, reason: entered.reason }, 422);\n }\n item = (await workItems.getForProject(resolved.orgId, resolved.factoryProjectId, item.id)) ?? item;\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.work_item.created',\n factoryProjectId: resolved.factoryProjectId,\n targets: [{ type: 'work_item', id: item.id, name: item.title }],\n metadata: { externalSource: item.externalSource, stages: item.stages },\n },\n });\n } else {\n // Source-key reuse: the POST updated an existing card, so audit it\n // as an update (plus stage/run events) instead of a false creation.\n const { stages: _stages, sessions: _sessions, ...boundedPatch } = input;\n await this.#auditWorkItemPatch({\n context: loose(c),\n item,\n previous: result.previous,\n patch: boundedPatch as unknown as Record<string, unknown>,\n });\n }\n return c.json({ workItem: toWireWorkItem(item) });\n } catch (error) {\n if (error instanceof WorkItemRelationError) {\n return c.json({ error: error.code, message: error.message }, 400);\n }\n throw error;\n }\n },\n }),\n\n // ── Authoritative stage transition ──────────────────────────────────────\n registerApiRoute('/web/factory/projects/:id/work-items/:workItemId/transition', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n const workItemId = loose(c).req.param('workItemId');\n if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: 'Work item not found' }, 404);\n const parsed = parseTransitionBody(await readJson(loose(c)));\n if (!parsed) return c.json({ error: 'invalid_transition_request' }, 400);\n if (!transitionService) {\n return c.json({ error: 'factory_transition_unavailable' }, 503);\n }\n await workItems.ensureReady();\n const result = await transitionService.transition({\n ...parsed,\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n workItemId,\n actor: { type: 'human', id: resolved.userId },\n ingress: {\n ...parsed.ingress,\n identity: `human:${resolved.userId}:${parsed.ingress.identity}`,\n },\n });\n await audit.emit({\n context: loose(c),\n input: {\n action:\n result.status === 'accepted'\n ? 'factory.work_item.stage_moved'\n : 'factory.work_item.transition_rejected',\n factoryProjectId: resolved.factoryProjectId,\n targets: [{ type: 'work_item', id: workItemId }],\n metadata: {\n transitionId: result.transitionId,\n ingressType: parsed.ingress.type,\n ruleSetVersion: transitionService.ruleSetVersion,\n ...(result.status === 'accepted'\n ? { to: result.stage, revision: result.revision }\n : { code: result.code, reason: result.reason }),\n },\n },\n });\n if (result.status === 'accepted') return c.json({ result });\n return c.json({ result }, result.code === 'stale' ? 409 : 422);\n },\n }),\n\n // ── Bind a Factory run before dispatching its kickoff ────────────────────\n registerApiRoute('/web/factory/projects/:id/runs/start', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n if (!startCoordinator) {\n return c.json({ error: 'factory_start_unavailable' }, 503);\n }\n const input = parseStartBody(await readJson(loose(c)), resolved, resolved.factoryProjectId);\n if (!input) return c.json({ error: 'invalid_factory_start' }, 400);\n input.requestContext = loose(c).get('requestContext');\n input.defaultModelId = resolved.defaultModelId ?? undefined;\n // This route is only reached by a person pressing a run action, so\n // reaching it is the commitment the approval gate is asking for.\n // Arming rides inside prepareRunStart's transaction so a crash can't\n // start the run while leaving its follow-up work waiting on approval.\n input.armAutonomy = true;\n if (\n !input.workItem.id &&\n ((input.workItem.input.stages ?? ['intake']).length !== 1 ||\n (input.workItem.input.stages ?? ['intake'])[0] !== 'intake')\n ) {\n return c.json(\n { error: 'governed_transition_required', message: 'Create the work item in Intake before starting it.' },\n 409,\n );\n }\n await workItems.ensureReady();\n let prepared: FactoryStartPreparedResult;\n try {\n prepared = await startCoordinator.prepare(input);\n } catch (error) {\n if (error instanceof FactoryStartTransitionError) {\n return c.json({ result: error.result }, error.result.code === 'stale' ? 409 : 422);\n }\n throw error;\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.run.started',\n factoryProjectId: resolved.factoryProjectId,\n targets: [{ type: 'work_item', id: prepared.workItemId }],\n metadata: {\n role: input.workItem.role,\n branch: prepared.branch,\n threadId: prepared.threadId,\n sessionId: prepared.sessionId,\n bindingId: prepared.bindingId,\n },\n },\n });\n return c.json({ prepared }, 202);\n },\n }),\n\n // ── Patch non-stage metadata / sessions / title ──────────────────────────\n registerApiRoute('/web/factory/work-items/:id', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n const id = loose(c).req.param('id');\n if (!id || !UUID_RE.test(id)) return c.json({ error: 'Work item not found' }, 404);\n\n const body = await readJson(loose(c));\n if (body === undefined) return c.json({ error: 'Invalid JSON body' }, 400);\n const patch = parseUpdateWorkItem(body);\n if (!patch) return c.json({ error: 'invalid_work_item_patch' }, 400);\n if (patch.stages !== undefined) {\n return c.json(\n { error: 'governed_transition_required', message: 'Use the Factory transition endpoint to move stages.' },\n 409,\n );\n }\n\n await workItems.ensureReady();\n try {\n const updated = await workItems.update({ orgId: tenant.orgId, id, userId: tenant.userId, patch });\n if (!updated) return c.json({ error: 'Work item not found' }, 404);\n await this.#auditWorkItemPatch({\n context: loose(c),\n item: updated.item,\n previous: updated.previous,\n patch: patch as Record<string, unknown>,\n });\n return c.json({ workItem: toWireWorkItem(updated.item) });\n } catch (error) {\n if (error instanceof WorkItemRelationError) {\n return c.json({ error: error.code, message: error.message }, 400);\n }\n throw error;\n }\n },\n }),\n\n // ── Remove a work item ───────────────────────────────────────────────────\n registerApiRoute('/web/factory/work-items/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n const id = loose(c).req.param('id');\n if (!id || !UUID_RE.test(id)) return c.json({ error: 'Work item not found' }, 404);\n\n await workItems.ensureReady();\n const deleted = await workItems.delete({ orgId: tenant.orgId, id });\n if (!deleted) return c.json({ error: 'Work item not found' }, 404);\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.work_item.deleted',\n factoryProjectId: deleted.factoryProjectId,\n targets: [{ type: 'work_item', id: deleted.id, name: deleted.title }],\n },\n });\n return c.json({ ok: true });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"work-items.js","names":["#resolveTenant","#resolveProject","#auditWorkItemPatch","#proposalRoute"],"sources":["../../src/routes/work-items.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for Factory work items (the kanban board).\n *\n * Registered alongside the other `/web/*` routes behind the host auth gate.\n * The board is org-wide: every route re-resolves the caller's `(orgId, userId)`\n * tenant and scopes reads/writes by `orgId`, so any org member sees and moves\n * the same cards while `created_by` / stage history record who acted.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type {\n FactoryStartCoordinator,\n FactoryStartPreparedResult,\n FactoryStartRequest,\n} from '../rules/start-coordinator.js';\nimport { FactoryStartTransitionError } from '../rules/start-coordinator.js';\nimport { roleForStage } from '../rules/transition-service.js';\nimport type { FactoryTransitionRequest, FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRuleBoard, WorkItemSource } from '../rules/types.js';\nimport { FACTORY_RULE_BOARDS, isFactoryRuleStage } from '../rules/types.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport { thresholdsOrDefault } from '../storage/domains/queue-health/base.js';\nimport type {\n CreateWorkItemInput,\n ExternalWorkItemSource,\n FactoryDeferredDecisionRecord,\n FactoryDispatchStatus,\n UpdateWorkItemInput,\n WorkItemPriorState,\n WorkItemRow,\n WorkItemSessionInput,\n WorkItemStage,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n FACTORY_PULL_REQUEST_RECONCILIATION_KEY,\n FACTORY_RULE_MATERIALIZATION_KEY,\n WorkItemRelationError,\n} from '../storage/domains/work-items/base.js';\nimport { computeFactoryMetrics, parseMetricsRange } from '../storage/domains/work-items/metrics.js';\nimport { buildAttentionRoutes, factoryDecisionType } from './attention.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface WorkItemRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Factory projects domain — validates the `:id` project belongs to the caller's org. */\n projects: FactoryProjectsStorage;\n /** Work-items domain backing the kanban board. */\n workItems: WorkItemsStorage;\n /** Comments domain — backs the mention attention provider. */\n comments: WorkItemCommentsStorage;\n /** Per-project queue-health threshold config. */\n queueHealth: QueueHealthStorage;\n /** Governed stage-transition service. Stage moves 503 when absent. */\n transitionService?: Pick<FactoryTransitionService, 'transition' | 'ruleSetVersion'>;\n /** Coordinator that binds a Factory run before dispatching its kickoff. */\n startCoordinator?: Pick<FactoryStartCoordinator, 'prepare'>;\n /** Materialized sessions, read to report which of the listed cards are being worked. */\n liveSessions: Pick<LiveSessions, 'isRunning'>;\n}\n\n/** The card as clients see it, without the dispatcher's internal bookkeeping. */\nfunction toWireWorkItem(item: WorkItemRow): WorkItemRow {\n if (\n !item.metadata ||\n (!(FACTORY_RULE_MATERIALIZATION_KEY in item.metadata) &&\n !(FACTORY_PULL_REQUEST_RECONCILIATION_KEY in item.metadata))\n ) {\n return item;\n }\n return { ...item, metadata: publicWorkItemMetadata(item.metadata) ?? {} };\n}\n\n/** Session ids of the listed cards whose agent run is in flight. */\nfunction runningSessionIds(items: WorkItemRow[], liveSessions: Pick<LiveSessions, 'isRunning'>): string[] {\n const running = new Set<string>();\n for (const item of items) {\n for (const { sessionId } of Object.values(item.sessions)) {\n if (liveSessions.isRunning(sessionId)) running.add(sessionId);\n }\n }\n return [...running];\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nconst MAX_STAGES = 16;\nconst MAX_STAGE_LENGTH = 64;\nconst MAX_METADATA_BYTES = 16 * 1024;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction validStages(value: unknown): value is WorkItemStage[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.length <= MAX_STAGES &&\n value.every(\n stage => typeof stage === 'string' && stage.length <= MAX_STAGE_LENGTH && /^[a-z0-9][a-z0-9_-]*$/i.test(stage),\n ) &&\n new Set(value).size === value.length\n );\n}\n\nfunction validMetadata(value: unknown): value is Record<string, unknown> | null {\n if (value === null) return true;\n if (!isRecord(value)) return false;\n try {\n return JSON.stringify(value).length <= MAX_METADATA_BYTES;\n } catch {\n return false;\n }\n}\n\nfunction publicWorkItemMetadata(value: Record<string, unknown> | null): Record<string, unknown> | null {\n if (value === null) return null;\n const {\n [FACTORY_RULE_MATERIALIZATION_KEY]: _materialization,\n [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: _reconciliation,\n ...metadata\n } = value;\n return metadata;\n}\n\nfunction parseExternalSource(value: unknown): ExternalWorkItemSource | null | undefined {\n if (value === undefined || value === null) return value;\n if (!isRecord(value)) return undefined;\n const { integrationId, type, externalId, url } = value;\n if (typeof integrationId !== 'string' || integrationId.length === 0 || integrationId.length > 128) return undefined;\n if (typeof type !== 'string' || type.length === 0 || type.length > 128) return undefined;\n if (typeof externalId !== 'string' || externalId.length === 0 || externalId.length > 512) return undefined;\n if (url !== undefined && (typeof url !== 'string' || url.length > 2048)) return undefined;\n return { integrationId, type, externalId, ...(url !== undefined ? { url } : {}) };\n}\n\nfunction parseParentWorkItemId(value: unknown): string | null | undefined {\n if (value === null) return null;\n if (typeof value !== 'string' || !UUID_RE.test(value)) return undefined;\n return value;\n}\n\nfunction parseSessions(value: unknown): Record<string, WorkItemSessionInput> | undefined {\n if (!isRecord(value)) return undefined;\n const out: Record<string, WorkItemSessionInput> = {};\n for (const [role, session] of Object.entries(value)) {\n if (!role || role.length > 64 || !isRecord(session)) return undefined;\n const { sessionId, branch, threadId } = session;\n if (typeof sessionId !== 'string' || sessionId.length === 0 || sessionId.length > 512) return undefined;\n if (typeof branch !== 'string' || branch.length === 0 || branch.length > 512) return undefined;\n if (typeof threadId !== 'string' || threadId.length === 0 || threadId.length > 512) return undefined;\n out[role] = { sessionId, branch, threadId };\n }\n return out;\n}\n\n/** Validate an untrusted create body. Unknown keys are dropped. */\nexport function parseCreateWorkItem(body: unknown): CreateWorkItemInput | null {\n if (!isRecord(body)) return null;\n const { externalSource, title, stages, sessions, metadata } = body;\n if (typeof title !== 'string' || title.trim().length === 0 || title.length > 500) return null;\n\n const hasParentWorkItemId = 'parentWorkItemId' in body;\n const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : undefined;\n if (hasParentWorkItemId && parentWorkItemId === undefined) return null;\n const parsedSource = parseExternalSource(externalSource);\n if (externalSource !== undefined && parsedSource === undefined) return null;\n if (stages !== undefined && !validStages(stages)) return null;\n const parsedSessions = sessions === undefined ? undefined : parseSessions(sessions);\n if (sessions !== undefined && parsedSessions === undefined) return null;\n let parsedMetadata: Record<string, unknown> | null | undefined;\n if (metadata !== undefined) {\n if (!validMetadata(metadata)) return null;\n parsedMetadata = publicWorkItemMetadata(metadata);\n }\n\n return {\n title: title.trim(),\n ...(parsedSource !== undefined ? { externalSource: parsedSource } : {}),\n ...(hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {}),\n ...(stages !== undefined ? { stages } : {}),\n ...(parsedSessions !== undefined ? { sessions: parsedSessions } : {}),\n ...(parsedMetadata !== undefined ? { metadata: parsedMetadata } : {}),\n };\n}\n\n/** Validate an untrusted patch body. Unknown keys are dropped. */\nexport function parseUpdateWorkItem(body: unknown): UpdateWorkItemInput | null {\n if (!isRecord(body)) return null;\n const { title, stages, sessions, metadata } = body;\n const hasParentWorkItemId = 'parentWorkItemId' in body;\n if (\n title === undefined &&\n stages === undefined &&\n sessions === undefined &&\n metadata === undefined &&\n !hasParentWorkItemId\n )\n return null;\n const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : undefined;\n if (hasParentWorkItemId && parentWorkItemId === undefined) return null;\n if (title !== undefined && (typeof title !== 'string' || title.trim().length === 0 || title.length > 500))\n return null;\n if (stages !== undefined && !validStages(stages)) return null;\n const parsedSessions = sessions === undefined ? undefined : parseSessions(sessions);\n if (sessions !== undefined && parsedSessions === undefined) return null;\n let parsedMetadata: Record<string, unknown> | null | undefined;\n if (metadata !== undefined) {\n if (!validMetadata(metadata)) return null;\n parsedMetadata = publicWorkItemMetadata(metadata);\n }\n\n return {\n ...(hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {}),\n ...(title !== undefined ? { title: title.trim() } : {}),\n ...(stages !== undefined ? { stages } : {}),\n ...(parsedSessions !== undefined ? { sessions: parsedSessions } : {}),\n ...(parsedMetadata !== undefined ? { metadata: parsedMetadata } : {}),\n };\n}\n\nasync function readJson(c: Context): Promise<unknown | undefined> {\n try {\n return await c.req.json();\n } catch {\n return undefined;\n }\n}\n\n/** Fields a PATCH touched, for the bounded `updated` event summary. */\nfunction patchedFields(patch: Record<string, unknown>): string[] {\n return Object.keys(patch).filter(key => patch[key] !== undefined);\n}\n\nfunction boundedText(value: unknown, max: number): string | undefined {\n if (typeof value !== 'string') return undefined;\n const normalized = value.trim();\n return normalized.length > 0 && normalized.length <= max ? normalized : undefined;\n}\n\nfunction parseTransitionBody(\n body: unknown,\n): Omit<FactoryTransitionRequest, 'orgId' | 'factoryProjectId' | 'workItemId' | 'actor'> | null {\n if (!isRecord(body)) return null;\n const board = FACTORY_RULE_BOARDS.includes(body.board as FactoryRuleBoard)\n ? (body.board as FactoryRuleBoard)\n : undefined;\n const stage = isFactoryRuleStage(body.stage) ? body.stage : undefined;\n const requestId = boundedText(body.requestId, 256);\n const cause = boundedText(body.cause, 256);\n if (\n !board ||\n !stage ||\n !requestId ||\n !UUID_RE.test(requestId) ||\n !cause ||\n !Number.isInteger(body.expectedRevision) ||\n Number(body.expectedRevision) < 1\n ) {\n return null;\n }\n return {\n board,\n stage,\n expectedRevision: Number(body.expectedRevision),\n ingress: { type: 'human', identity: requestId },\n cause,\n };\n}\n\nfunction parseInvocation(value: unknown): FactoryStartRequest['invocation'] | undefined | null {\n if (value === undefined) return undefined;\n if (!isRecord(value)) return null;\n if (value.type === 'prompt') {\n const prompt = boundedText(value.prompt, 16_384);\n return prompt ? { type: 'prompt', prompt } : null;\n }\n if (value.type === 'skill') {\n const skillName = boundedText(value.skillName, 64);\n const args = typeof value.arguments === 'string' && value.arguments.length <= 16_384 ? value.arguments : undefined;\n return skillName && args !== undefined ? { type: 'skill', skillName, arguments: args } : null;\n }\n return null;\n}\n\nfunction parseStartBody(\n body: unknown,\n tenant: { orgId: string; userId: string },\n factoryProjectId: string,\n): FactoryStartRequest | null {\n if (!isRecord(body) || !isRecord(body.workItem)) return null;\n const input = parseCreateWorkItem(body.workItem.input);\n const sessionId = boundedText(body.sessionId, 256);\n const threadTitle = boundedText(body.threadTitle, 512);\n const kickoffKey = boundedText(body.kickoffKey, 256);\n const invocation = parseInvocation(body.invocation);\n const destinationStage = isFactoryRuleStage(body.destinationStage) ? body.destinationStage : undefined;\n const role = boundedText(body.workItem.role, 32);\n const id = body.workItem.id === undefined ? undefined : boundedText(body.workItem.id, 64);\n if (body.workItem.id !== undefined && (!id || !UUID_RE.test(id))) return null;\n if (\n !input ||\n !sessionId ||\n !UUID_RE.test(sessionId) ||\n !threadTitle ||\n !kickoffKey ||\n !UUID_RE.test(kickoffKey) ||\n invocation === null ||\n !destinationStage ||\n !role\n ) {\n return null;\n }\n const threadTags = isRecord(body.threadTags)\n ? Object.fromEntries(\n Object.entries(body.threadTags)\n .filter(\n (entry): entry is [string, string] =>\n boundedText(entry[0], 64) !== undefined && boundedText(entry[1], 256) !== undefined,\n )\n .map(([key, value]) => [key, value.trim()]),\n )\n : undefined;\n return {\n ...tenant,\n factoryProjectId,\n sessionId,\n threadTitle,\n threadTags,\n kickoffKey,\n preapprovePlans: body.preapprovePlans === true,\n invocation,\n destinationStage,\n workItem: { id, role, input },\n };\n}\n\nconst DECISION_STATUSES = new Set<FactoryDispatchStatus>([\n 'pending',\n 'proposed',\n 'dismissed',\n 'superseded',\n 'leased',\n 'retry',\n 'succeeded',\n 'failed',\n]);\nconst DEFAULT_DECISION_PAGE_SIZE = 25;\nconst MAX_DECISION_PAGE_SIZE = 50;\n\nfunction parseDecisionStatuses(raw: string | undefined): FactoryDispatchStatus[] | undefined {\n if (!raw) return undefined;\n const statuses = [...new Set(raw.split(',').map(status => status.trim()))].filter(\n (status): status is FactoryDispatchStatus => DECISION_STATUSES.has(status as FactoryDispatchStatus),\n );\n return statuses.length > 0 ? statuses : undefined;\n}\n\nfunction parseDecisionLimit(raw: string | undefined): number {\n const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_DECISION_PAGE_SIZE;\n if (!Number.isFinite(parsed)) return DEFAULT_DECISION_PAGE_SIZE;\n return Math.max(1, Math.min(MAX_DECISION_PAGE_SIZE, parsed));\n}\n\nfunction encodeDecisionCursor(decision: FactoryDeferredDecisionRecord): string {\n return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), 'utf8').toString('base64url');\n}\n\nfunction parseDecisionCursor(raw: string | undefined): { createdAt: Date; id: string } | undefined {\n if (!raw) return undefined;\n try {\n const decoded = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8')) as unknown;\n if (\n !Array.isArray(decoded) ||\n decoded.length !== 2 ||\n typeof decoded[0] !== 'string' ||\n typeof decoded[1] !== 'string'\n ) {\n return undefined;\n }\n const createdAt = new Date(decoded[0]);\n if (Number.isNaN(createdAt.getTime()) || !UUID_RE.test(decoded[1])) return undefined;\n return { createdAt, id: decoded[1] };\n } catch {\n return undefined;\n }\n}\n\n/** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */\nfunction summaryRole(decision: Record<string, unknown>): string | null {\n if (typeof decision.role === 'string') return decision.role.slice(0, 32);\n if (decision.type !== 'transition') return null;\n const board = decision.board;\n const stage = decision.stage;\n if ((board !== 'work' && board !== 'review') || !isFactoryRuleStage(stage)) return null;\n return roleForStage(board, stage);\n}\n\n/** A linked-card decision names where the card is synced from, so the UI can say \"GitHub\" rather than \"a linked card\". */\nfunction summarySource(decision: Record<string, unknown>): WorkItemSource | null {\n if (decision.type !== 'upsertLinkedWorkItem') return null;\n const source = decision.source;\n return source === 'github-issue' || source === 'github-pr' || source === 'linear-issue' || source === 'manual'\n ? source\n : null;\n}\n\nfunction decisionSummary(decision: FactoryDeferredDecisionRecord) {\n return {\n id: decision.id,\n evaluationId: decision.evaluationId,\n workItemId: decision.workItemId,\n type: factoryDecisionType(decision),\n role: summaryRole(decision.decision),\n source: summarySource(decision.decision),\n status: decision.status,\n attempts: decision.attempts,\n failureOccurrence: decision.failureOccurrence,\n failureCode: decision.failureCode,\n canRetry: factoryDispatchFailureMetadata(decision.failureCode).canRetry,\n lastError: decision.lastError?.slice(0, 512) ?? null,\n createdAt: decision.createdAt.toISOString(),\n updatedAt: decision.updatedAt.toISOString(),\n completedAt: decision.completedAt?.toISOString() ?? null,\n };\n}\n\nexport class WorkItemRoutes extends Route<WorkItemRoutesDeps> {\n /** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'The Factory board requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n /**\n * Resolve the tenant AND the org-owned project from the `:id` param. Work\n * items hang off a project, so listing/creating requires the project to\n * exist in the caller's org.\n */\n async #resolveProject(\n c: Context,\n ): Promise<\n { orgId: string; userId: string; factoryProjectId: string; defaultModelId: string | null } | { response: Response }\n > {\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const { projects } = this.deps;\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n return { ...tenant, factoryProjectId: projectId, defaultModelId: project.defaultModelId };\n }\n\n /**\n * Emit the audit events a successful work-item PATCH implies: always\n * `updated`, plus `stage_moved` when the stages actually changed and one\n * `run.started` per session role the patch introduced.\n */\n async #auditWorkItemPatch({\n context,\n item,\n previous,\n patch,\n }: {\n context: Context;\n item: WorkItemRow;\n previous: WorkItemPriorState;\n patch: Record<string, unknown>;\n }): Promise<void> {\n const { audit } = this.deps;\n const target = { type: 'work_item', id: item.id, name: item.title };\n await audit.emit({\n context,\n input: {\n action: 'factory.work_item.updated',\n factoryProjectId: item.factoryProjectId,\n targets: [target],\n metadata: { fields: patchedFields(patch) },\n },\n });\n\n const stagesChanged =\n patch.stages !== undefined &&\n (previous.stages.length !== item.stages.length || previous.stages.some((s, i) => s !== item.stages[i]));\n if (stagesChanged) {\n await audit.emit({\n context,\n input: {\n action: 'factory.work_item.stage_moved',\n factoryProjectId: item.factoryProjectId,\n targets: [target],\n metadata: { from: previous.stages, to: item.stages },\n },\n });\n }\n\n const newRoles = Object.keys(item.sessions).filter(role => !previous.sessionRoles.includes(role));\n for (const role of newRoles) {\n const session = item.sessions[role];\n await audit.emit({\n context,\n input: {\n action: 'factory.run.started',\n factoryProjectId: item.factoryProjectId,\n targets: [target],\n metadata: {\n role,\n branch: session?.branch,\n threadId: session?.threadId,\n sessionId: session?.sessionId,\n },\n },\n });\n }\n }\n\n /** Releasing a parked run and dropping it: same request, opposite outcomes, both audited as consent. */\n #proposalRoute({\n verb,\n settle,\n }: {\n verb: 'approve' | 'dismiss';\n settle: (\n orgId: string,\n factoryProjectId: string,\n decisionId: string,\n now: Date,\n userId: string,\n ) => Promise<FactoryDeferredDecisionRecord | null>;\n }): ApiRoute {\n const { audit, workItems } = this.deps;\n return registerApiRoute(`/web/factory/projects/:id/decisions/:decisionId/${verb}`, {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const decisionId = context.req.param('decisionId');\n if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: 'invalid_decision_id' }, 422);\n await workItems.ensureReady();\n const now = new Date();\n const decision = await settle(resolved.orgId, resolved.factoryProjectId, decisionId, now, resolved.userId);\n if (!decision) return c.json({ error: 'decision_not_proposed' }, 409);\n // Releasing a proposal is a person taking the item on. Approval arms the\n // item's autonomy inside the same storage transaction (see\n // approveDeferredDecision), so follow-up runs no longer wait for approval.\n await audit.emit({\n context,\n input: {\n action: verb === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n factoryProjectId: resolved.factoryProjectId,\n targets: decision.workItemId\n ? [{ type: 'work_item', id: decision.workItemId }]\n : [{ type: 'rule_decision', id: decision.id }],\n metadata: { decisionId: decision.id, effect: factoryDecisionType(decision) },\n },\n });\n return c.json({ decision: decisionSummary(decision) });\n },\n });\n }\n\n /** Build the Factory work-item routes as Mastra `apiRoutes`. */\n routes(): ApiRoute[] {\n const { audit, workItems, queueHealth, transitionService, startCoordinator, liveSessions } = this.deps;\n return [\n // ── List the org's work items for a project, and which are being worked ─\n registerApiRoute('/web/factory/projects/:id/work-items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n await workItems.ensureReady();\n const items = await workItems.list({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n });\n return c.json({\n workItems: items.map(toWireWorkItem),\n runningSessionIds: runningSessionIds(items, liveSessions),\n });\n },\n }),\n\n // ── Flow metrics aggregated over the project's work items ───────────────\n registerApiRoute('/web/factory/projects/:id/metrics', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n const { windowStart, windowEnd } = parseMetricsRange(\n loose(c).req.query('from'),\n loose(c).req.query('to'),\n new Date(),\n );\n await workItems.ensureReady();\n const items = await workItems.list({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n });\n return c.json({ metrics: computeFactoryMetrics(items, { windowStart, windowEnd }) });\n },\n }),\n\n // ── Per-project queue-health age-threshold config (seconds) ─────────────\n registerApiRoute('/web/factory/projects/:id/health/thresholds', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n await queueHealth.ensureReady();\n const stored = await queueHealth.getConfig(resolved.orgId, resolved.factoryProjectId);\n // Validate at the read choke point: `getConfig` round-trips a stored\n // JSONB row, and only `saveConfig` validates on write — a corrupted or\n // hand-edited row (empty / non-ascending) would otherwise flow to the\n // chart and invert bucket colors. Fall back to the default on invalid.\n return c.json({ thresholds: thresholdsOrDefault(stored) });\n },\n }),\n\n // ── Bounded durable rule-decision status ────────────────────────────────\n registerApiRoute('/web/factory/projects/:id/decisions', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n\n const cursorRaw = context.req.query('before');\n const before = parseDecisionCursor(cursorRaw);\n if (cursorRaw && !before) return c.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n const page = await workItems.listDeferredDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: parseDecisionStatuses(context.req.query('statuses')),\n before,\n limit: parseDecisionLimit(context.req.query('limit')),\n });\n const last = page.decisions.at(-1);\n return c.json({\n decisions: page.decisions.map(decisionSummary),\n ...(page.hasMore && last ? { nextCursor: encodeDecisionCursor(last) } : {}),\n });\n },\n }),\n\n ...buildAttentionRoutes({\n workItems,\n comments: this.deps.comments,\n resolveProject: context => this.#resolveProject(loose(context)),\n }),\n\n this.#proposalRoute({ verb: 'approve', settle: workItems.approveDeferredDecision.bind(workItems) }),\n this.#proposalRoute({ verb: 'dismiss', settle: workItems.dismissDeferredDecision.bind(workItems) }),\n\n registerApiRoute('/web/factory/projects/:id/decisions/:decisionId/retry', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const decisionId = context.req.param('decisionId');\n if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: 'invalid_decision_id' }, 422);\n await workItems.ensureReady();\n const current = await workItems.getDeferredDecision(resolved.orgId, resolved.factoryProjectId, decisionId);\n if (\n !current ||\n current.status !== 'failed' ||\n !factoryDispatchFailureMetadata(current.failureCode).canRetry\n ) {\n return c.json({ error: 'decision_not_retryable' }, 409);\n }\n const decision = await workItems.retryDeferredDecision(\n resolved.orgId,\n resolved.factoryProjectId,\n decisionId,\n new Date(),\n );\n if (!decision) return c.json({ error: 'decision_not_retryable' }, 409);\n return c.json({ decision: decisionSummary(decision) });\n },\n }),\n\n // ── Create (upsert on sourceKey) a work item ─────────────────────────────\n registerApiRoute('/web/factory/projects/:id/work-items', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n\n const body = await readJson(loose(c));\n if (body === undefined) return c.json({ error: 'Invalid JSON body' }, 400);\n const input = parseCreateWorkItem(body);\n if (!input) return c.json({ error: 'invalid_work_item' }, 400);\n if ((input.stages ?? ['intake']).length !== 1 || (input.stages ?? ['intake'])[0] !== 'intake') {\n return c.json(\n { error: 'governed_transition_required', message: 'New work items must enter through Factory intake.' },\n 409,\n );\n }\n\n await workItems.ensureReady();\n try {\n const result = await workItems.upsert({\n orgId: resolved.orgId,\n userId: resolved.userId,\n factoryProjectId: resolved.factoryProjectId,\n input,\n reuseMode: 'non-stage',\n });\n let item = result.item;\n if (result.created) {\n if (!transitionService) {\n await workItems.delete({ orgId: resolved.orgId, id: item.id });\n return c.json({ error: 'factory_transitions_unavailable' }, 503);\n }\n const entered = await transitionService.transition({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n workItemId: item.id,\n board: item.externalSource?.type === 'pull-request' ? 'review' : 'work',\n stage: 'intake',\n expectedRevision: item.revision,\n actor: { type: 'human', id: resolved.userId },\n ingress: { type: 'human', identity: `work-item:${item.id}:initial-entry` },\n cause: 'work_item_created',\n initialEntry: true,\n });\n if (entered.status === 'rejected') {\n await workItems.delete({ orgId: resolved.orgId, id: item.id });\n return c.json({ status: 'rejected', code: entered.code, reason: entered.reason }, 422);\n }\n item = (await workItems.getForProject(resolved.orgId, resolved.factoryProjectId, item.id)) ?? item;\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.work_item.created',\n factoryProjectId: resolved.factoryProjectId,\n targets: [{ type: 'work_item', id: item.id, name: item.title }],\n metadata: { externalSource: item.externalSource, stages: item.stages },\n },\n });\n } else {\n // Source-key reuse: the POST updated an existing card, so audit it\n // as an update (plus stage/run events) instead of a false creation.\n const { stages: _stages, sessions: _sessions, ...boundedPatch } = input;\n await this.#auditWorkItemPatch({\n context: loose(c),\n item,\n previous: result.previous,\n patch: boundedPatch as unknown as Record<string, unknown>,\n });\n }\n return c.json({ workItem: toWireWorkItem(item) });\n } catch (error) {\n if (error instanceof WorkItemRelationError) {\n return c.json({ error: error.code, message: error.message }, 400);\n }\n throw error;\n }\n },\n }),\n\n // ── Authoritative stage transition ──────────────────────────────────────\n registerApiRoute('/web/factory/projects/:id/work-items/:workItemId/transition', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n const workItemId = loose(c).req.param('workItemId');\n if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: 'Work item not found' }, 404);\n const parsed = parseTransitionBody(await readJson(loose(c)));\n if (!parsed) return c.json({ error: 'invalid_transition_request' }, 400);\n if (!transitionService) {\n return c.json({ error: 'factory_transition_unavailable' }, 503);\n }\n await workItems.ensureReady();\n const result = await transitionService.transition({\n ...parsed,\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n workItemId,\n actor: { type: 'human', id: resolved.userId },\n ingress: {\n ...parsed.ingress,\n identity: `human:${resolved.userId}:${parsed.ingress.identity}`,\n },\n });\n await audit.emit({\n context: loose(c),\n input: {\n action:\n result.status === 'accepted'\n ? 'factory.work_item.stage_moved'\n : 'factory.work_item.transition_rejected',\n factoryProjectId: resolved.factoryProjectId,\n targets: [{ type: 'work_item', id: workItemId }],\n metadata: {\n transitionId: result.transitionId,\n ingressType: parsed.ingress.type,\n ruleSetVersion: transitionService.ruleSetVersion,\n ...(result.status === 'accepted'\n ? { to: result.stage, revision: result.revision }\n : { code: result.code, reason: result.reason }),\n },\n },\n });\n if (result.status === 'accepted') return c.json({ result });\n return c.json({ result }, result.code === 'stale' ? 409 : 422);\n },\n }),\n\n // ── Bind a Factory run before dispatching its kickoff ────────────────────\n registerApiRoute('/web/factory/projects/:id/runs/start', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n if (!startCoordinator) {\n return c.json({ error: 'factory_start_unavailable' }, 503);\n }\n const input = parseStartBody(await readJson(loose(c)), resolved, resolved.factoryProjectId);\n if (!input) return c.json({ error: 'invalid_factory_start' }, 400);\n input.requestContext = loose(c).get('requestContext');\n input.defaultModelId = resolved.defaultModelId ?? undefined;\n // This route is only reached by a person pressing a run action, so\n // reaching it is the commitment the approval gate is asking for.\n // Arming rides inside prepareRunStart's transaction so a crash can't\n // start the run while leaving its follow-up work waiting on approval.\n input.armAutonomy = true;\n if (\n !input.workItem.id &&\n ((input.workItem.input.stages ?? ['intake']).length !== 1 ||\n (input.workItem.input.stages ?? ['intake'])[0] !== 'intake')\n ) {\n return c.json(\n { error: 'governed_transition_required', message: 'Create the work item in Intake before starting it.' },\n 409,\n );\n }\n await workItems.ensureReady();\n let prepared: FactoryStartPreparedResult;\n try {\n prepared = await startCoordinator.prepare(input);\n } catch (error) {\n if (error instanceof FactoryStartTransitionError) {\n return c.json({ result: error.result }, error.result.code === 'stale' ? 409 : 422);\n }\n throw error;\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.run.started',\n factoryProjectId: resolved.factoryProjectId,\n targets: [{ type: 'work_item', id: prepared.workItemId }],\n metadata: {\n role: input.workItem.role,\n branch: prepared.branch,\n threadId: prepared.threadId,\n sessionId: prepared.sessionId,\n bindingId: prepared.bindingId,\n },\n },\n });\n return c.json({ prepared }, 202);\n },\n }),\n\n // ── Patch non-stage metadata / sessions / title ──────────────────────────\n registerApiRoute('/web/factory/work-items/:id', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n const id = loose(c).req.param('id');\n if (!id || !UUID_RE.test(id)) return c.json({ error: 'Work item not found' }, 404);\n\n const body = await readJson(loose(c));\n if (body === undefined) return c.json({ error: 'Invalid JSON body' }, 400);\n const patch = parseUpdateWorkItem(body);\n if (!patch) return c.json({ error: 'invalid_work_item_patch' }, 400);\n if (patch.stages !== undefined) {\n return c.json(\n { error: 'governed_transition_required', message: 'Use the Factory transition endpoint to move stages.' },\n 409,\n );\n }\n\n await workItems.ensureReady();\n try {\n const updated = await workItems.update({ orgId: tenant.orgId, id, userId: tenant.userId, patch });\n if (!updated) return c.json({ error: 'Work item not found' }, 404);\n await this.#auditWorkItemPatch({\n context: loose(c),\n item: updated.item,\n previous: updated.previous,\n patch: patch as Record<string, unknown>,\n });\n return c.json({ workItem: toWireWorkItem(updated.item) });\n } catch (error) {\n if (error instanceof WorkItemRelationError) {\n return c.json({ error: error.code, message: error.message }, 400);\n }\n throw error;\n }\n },\n }),\n\n // ── Remove a work item ───────────────────────────────────────────────────\n registerApiRoute('/web/factory/work-items/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n const id = loose(c).req.param('id');\n if (!id || !UUID_RE.test(id)) return c.json({ error: 'Work item not found' }, 404);\n\n await workItems.ensureReady();\n const deleted = await workItems.delete({ orgId: tenant.orgId, id });\n if (!deleted) return c.json({ error: 'Work item not found' }, 404);\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.work_item.deleted',\n factoryProjectId: deleted.factoryProjectId,\n targets: [{ type: 'work_item', id: deleted.id, name: deleted.title }],\n },\n });\n return c.json({ ok: true });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
1
+ import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
2
2
  import type { FactoryRuleActor, FactoryRuleBoard, FactoryRuleCausalEntry, FactoryRuleStage, FactoryTriageType, FactoryRules, FactoryTransitionResult } from './types.js';
3
3
  export interface FactoryTransitionRequest {
4
4
  orgId: string;
@@ -43,6 +43,17 @@ export interface FactoryTransitionServiceOptions {
43
43
  * `onTerminalStage` before returning (default 30s). The cleanup continues
44
44
  * in the background past the bound. */
45
45
  terminalCleanupTimeoutMs?: number;
46
+ /**
47
+ * Called after a transition first records a person's acceptance of a
48
+ * non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures
49
+ * are swallowed, the committed transition never depends on it.
50
+ */
51
+ onAccepted?: (args: {
52
+ orgId: string;
53
+ factoryProjectId: string;
54
+ workItemId: string;
55
+ item: WorkItemRow;
56
+ }) => Promise<void> | void;
46
57
  }
47
58
  export declare function currentStage(stages: readonly string[]): FactoryRuleStage | undefined;
48
59
  export declare function roleForStage(board: FactoryRuleBoard, stage: FactoryRuleStage): string;
@@ -1 +1 @@
1
- {"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAEV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EAEtB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAEZ,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAuBpB,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjH,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAChD,iHAAiH;IACjH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qHAAqH;IACrH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B;;2CAEuC;IACvC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAuBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAIpF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAKrF;AAuED,qBAAa,wBAAwB;;gBAOvB,OAAO,EAAE,+BAA+B;IAQpD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAEK,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC;CA4PtF"}
1
+ {"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAEV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EAEtB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAEZ,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAuBpB,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjH,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAChD,iHAAiH;IACjH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qHAAqH;IACrH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B;;2CAEuC;IACvC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,WAAW,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5B;AAuBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAIpF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAKrF;AAwFD,qBAAa,wBAAwB;;gBAQvB,OAAO,EAAE,+BAA+B;IASpD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAEK,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC;CAwRtF"}
@@ -72,6 +72,15 @@ function isHumanTransition(request) {
72
72
  function requiresHumanApproval(triageType) {
73
73
  return triageType !== void 0 && triageType !== null && triageType !== "bug";
74
74
  }
75
+ function isAtRest(stage) {
76
+ return stage === "intake" || stage === "triage";
77
+ }
78
+ function entersWork(stage) {
79
+ return stage === "planning" || stage === "execute";
80
+ }
81
+ function acceptsItem(request) {
82
+ return isHumanTransition(request) && entersWork(request.stage);
83
+ }
75
84
  function ruleFailure(error) {
76
85
  return {
77
86
  code: "rule_error",
@@ -95,11 +104,13 @@ var FactoryTransitionService = class {
95
104
  #timeoutMs;
96
105
  #onTerminalStage;
97
106
  #terminalCleanupTimeoutMs;
107
+ #onAccepted;
98
108
  constructor(options) {
99
109
  this.#rules = options.rules;
100
110
  this.#storage = options.storage;
101
111
  this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;
102
112
  this.#onTerminalStage = options.onTerminalStage;
113
+ this.#onAccepted = options.onAccepted;
103
114
  this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;
104
115
  }
105
116
  get ruleSetVersion() {
@@ -122,7 +133,7 @@ var FactoryTransitionService = class {
122
133
  if (!fromStage) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not have one canonical Factory stage.");
123
134
  if (isTriageAgent(request.actor) && request.triageType === void 0) return this.#commitRejection(request, transitionId, "invalid_transition", "Triage transitions must report a structured triage classification.");
124
135
  if (item.triageType && request.triageType && item.triageType !== request.triageType) return this.#commitRejection(request, transitionId, "forbidden", "The persisted triage classification cannot be changed by a later transition.");
125
- if (requiresHumanApproval(item.triageType ?? request.triageType) && (request.stage === "planning" || request.stage === "execute") && !isHumanTransition(request)) return this.#commitRejection(request, transitionId, "approval_required", "A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.");
136
+ if (requiresHumanApproval(item.triageType ?? request.triageType) && entersWork(request.stage) && isAtRest(fromStage) && !isHumanTransition(request) && !item.acceptedAt) return this.#commitRejection(request, transitionId, "approval_required", "A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.");
126
137
  if (request.actor.type === "agent" && !isWorkingFactoryRuleStage(fromStage) && isWorkingFactoryRuleStage(request.stage) && externallyAuthoredWorkItem(item)) return this.#commitRejection(request, transitionId, "approval_required", "This card comes from outside the write-access circle; a person must resume it from the Factory board.");
127
138
  const humanBoardDrag = request.actor.type === "human" && request.cause === "board_drag" && fromStage !== request.stage;
128
139
  const contextBase = {
@@ -212,7 +223,10 @@ var FactoryTransitionService = class {
212
223
  } : ruleFailure(error)
213
224
  };
214
225
  }
215
- return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ? consentEffect(request, humanBoardDrag) : {});
226
+ return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ? {
227
+ ...consentEffect(request, humanBoardDrag),
228
+ accept: acceptsItem(request) && !item.acceptedAt
229
+ } : {});
216
230
  }
217
231
  async #commitRejection(request, transitionId, code, reason) {
218
232
  return this.#commit(request, transitionId, {
@@ -225,6 +239,7 @@ var FactoryTransitionService = class {
225
239
  const committed = await this.#storage.commitTransition({
226
240
  autonomy: options.autonomy,
227
241
  consentedBy: options.consentedBy,
242
+ ...options.accept ? { accept: true } : {},
228
243
  orgId: request.orgId,
229
244
  factoryProjectId: request.factoryProjectId,
230
245
  workItemId: request.workItemId,
@@ -243,6 +258,17 @@ var FactoryTransitionService = class {
243
258
  });
244
259
  if (committed.status === "missing") return rejection(transitionId, request.workItemId, "invalid_transition", "Work item not found.");
245
260
  const result = committed.result;
261
+ if (this.#onAccepted && options.accept && committed.status === "committed" && result.status === "accepted" && committed.item?.acceptedAt) {
262
+ const item = committed.item;
263
+ Promise.resolve(this.#onAccepted({
264
+ orgId: request.orgId,
265
+ factoryProjectId: request.factoryProjectId,
266
+ workItemId: request.workItemId,
267
+ item
268
+ })).catch((error) => {
269
+ console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);
270
+ });
271
+ }
246
272
  if (this.#onTerminalStage && result.status === "accepted" && TERMINAL_STAGES.has(result.stage)) {
247
273
  let timer;
248
274
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"transition-service.js","names":["#rules","#storage","#timeoutMs","#onTerminalStage","#terminalCleanupTimeoutMs","#commitRejection","#commit"],"sources":["../../src/rules/transition-service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryStageRules } from './resolve.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleActor,\n FactoryRuleBoard,\n FactoryRuleCausalEntry,\n FactoryRuleRejectionCode,\n FactoryRuleStage,\n FactoryTriageType,\n FactoryRules,\n FactoryStageRuleContext,\n FactoryTransitionResult,\n} from './types.js';\nimport {\n externallyAuthoredWorkItem,\n factoryRuleSourceForWorkItem,\n isFactoryRuleStage,\n isWorkingFactoryRuleStage,\n workItemSource,\n} from './types.js';\nimport {\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n validateFactoryRuleDecision,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\nconst MAX_REJECTION_REASON = 512;\nconst TERMINAL_STAGES: ReadonlySet<FactoryRuleStage> = new Set(['done', 'canceled']);\n/** Longest a committed transition waits for terminal resource cleanup. Cleanup\n * reattaches remote sandboxes, so a hung provider call must not leave the\n * already-committed transition request pending; past this bound the cleanup\n * keeps running in the background as pure best-effort. */\nconst TERMINAL_CLEANUP_TIMEOUT_MS = 30_000;\n\nexport interface FactoryTransitionRequest {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n expectedRevision: number;\n actor: FactoryRuleActor;\n ingress: { type: 'human' | 'agent' | 'toolResult' | 'github' | 'rule'; identity: string; transitionId?: string };\n cause: string;\n causalChain?: readonly FactoryRuleCausalEntry[];\n /** Internal materialization path: evaluate only the destination onEnter leaf even when already at that stage. */\n initialEntry?: boolean;\n /** Re-runs the stage's entry rules when the item already holds that stage, to restart work the entry invalidated. */\n reenter?: boolean;\n /** Structured verdict required from a bound triage-agent terminal request. */\n triageType?: FactoryTriageType;\n}\n\nexport interface FactoryTransitionServiceOptions {\n rules: FactoryRules;\n storage: WorkItemsStorage;\n timeoutMs?: number;\n /**\n * Called after a transition commits into a terminal stage (`done` /\n * `canceled`) — the point where the item's sessions stop receiving runs, so\n * resources they hold (e.g. sandboxes) can be released for reuse. Awaited,\n * but failures are swallowed: releasing resources must never break or roll\n * back the committed transition.\n */\n onTerminalStage?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n stage: FactoryRuleStage;\n }) => Promise<void> | void;\n /** Upper bound on how long a committed transition waits for\n * `onTerminalStage` before returning (default 30s). The cleanup continues\n * in the background past the bound. */\n terminalCleanupTimeoutMs?: number;\n}\n\nfunction rejection(\n transitionId: string,\n itemId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n): FactoryTransitionResult {\n return { status: 'rejected', transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };\n}\n\nfunction actorId(actor: FactoryRuleActor): string {\n switch (actor.type) {\n case 'human':\n case 'system':\n return actor.id;\n case 'agent':\n return `agent:${actor.bindingId}`;\n case 'github':\n return `github:${actor.login}`;\n }\n}\n\nexport function currentStage(stages: readonly string[]): FactoryRuleStage | undefined {\n if (stages.length !== 1) return undefined;\n const stage = stages[0];\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function roleForStage(board: FactoryRuleBoard, stage: FactoryRuleStage): string {\n if (board === 'review') return 'review';\n if (stage === 'triage') return 'triage';\n if (stage === 'planning') return 'plan';\n return 'work';\n}\n\ninterface TransitionConsentOptions {\n autonomy?: 'arm' | 'disarm';\n consentedBy?: string;\n}\n\n// Entering a resting lane disarms whoever rests it; only a person's drag into a working lane arms.\nfunction transitionConsent(stage: FactoryRuleStage, humanBoardDrag: boolean): 'arm' | 'disarm' | undefined {\n if (!isWorkingFactoryRuleStage(stage)) return 'disarm';\n return humanBoardDrag ? 'arm' : undefined;\n}\n\n// An event arriving as data (GitHub, sweeps) never pre-approves the runs its transition queues.\nfunction bearsConsent(actor: FactoryRuleActor): boolean {\n return actor.type === 'human' || actor.type === 'agent';\n}\n\n// Rides the transition's own revision-checked commit, so a stale or rejected commit flips nothing.\nfunction consentEffect(request: FactoryTransitionRequest, humanBoardDrag: boolean): TransitionConsentOptions {\n const autonomy = transitionConsent(request.stage, humanBoardDrag);\n return bearsConsent(request.actor) ? { autonomy, consentedBy: actorId(request.actor) } : { autonomy };\n}\n\ntype RunStartDecision = Extract<FactoryCommitDecision, { type: 'invokeSkill' | 'sendMessage' }>;\n\nfunction startsRun(decision: FactoryCommitDecision): decision is RunStartDecision {\n return decision.type === 'invokeSkill' || (decision.type === 'sendMessage' && decision.prepareBinding === true);\n}\n\n// Answering a recorded run start, or the role's own mid-run agent, with a run would start a second one.\nfunction runAlreadyUnderway(request: FactoryTransitionRequest, decision: RunStartDecision): boolean {\n if (request.cause === 'run_start') return true;\n return request.actor.type === 'agent' && request.actor.role === decision.role;\n}\n\nfunction stageTransitionMessage(fromStage: FactoryRuleStage, toStage: FactoryRuleStage): string {\n return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;\n}\n\nfunction isTriageAgent(actor: FactoryRuleActor): actor is Extract<FactoryRuleActor, { type: 'agent' }> {\n return actor.type === 'agent' && actor.role === 'triage';\n}\n\nfunction isHumanTransition(request: FactoryTransitionRequest): boolean {\n return request.actor.type === 'human' && request.ingress.type === 'human';\n}\n\nfunction requiresHumanApproval(triageType: FactoryTriageType | null | undefined): boolean {\n return triageType !== undefined && triageType !== null && triageType !== 'bug';\n}\n\nfunction ruleFailure(error: unknown): { code: FactoryRuleRejectionCode; reason: string } {\n return {\n code: 'rule_error',\n reason: error instanceof Error ? `Factory rule failed: ${error.message}` : 'Factory rule failed.',\n };\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), timeoutMs);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryTransitionService {\n readonly #rules: FactoryRules;\n readonly #storage: WorkItemsStorage;\n readonly #timeoutMs: number;\n readonly #onTerminalStage: FactoryTransitionServiceOptions['onTerminalStage'];\n readonly #terminalCleanupTimeoutMs: number;\n\n constructor(options: FactoryTransitionServiceOptions) {\n this.#rules = options.rules;\n this.#storage = options.storage;\n this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;\n this.#onTerminalStage = options.onTerminalStage;\n this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;\n }\n\n get ruleSetVersion(): string {\n return this.#rules.version;\n }\n\n async transition(request: FactoryTransitionRequest): Promise<FactoryTransitionResult> {\n const replay = await this.#storage.getTransitionResultByIngress(\n request.orgId,\n request.factoryProjectId,\n request.ingress.identity,\n );\n if (replay) return replay as unknown as FactoryTransitionResult;\n\n const transitionId = request.ingress.transitionId ?? randomUUID();\n const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });\n if (!item) {\n return this.#commitRejection(request, transitionId, 'invalid_transition', 'Work item not found.');\n }\n\n if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n return this.#commitRejection(\n request,\n transitionId,\n 'causal_depth_exceeded',\n 'Factory rule causal depth exceeded.',\n );\n }\n const itemSource = workItemSource(item.externalSource);\n const source = factoryRuleSourceForWorkItem(itemSource);\n if ((request.board === 'review') !== (source === 'pullRequest')) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not belong to the requested board.',\n );\n }\n const fromStage = currentStage(item.stages);\n if (!fromStage) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not have one canonical Factory stage.',\n );\n }\n\n if (isTriageAgent(request.actor) && request.triageType === undefined) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'Triage transitions must report a structured triage classification.',\n );\n }\n if (item.triageType && request.triageType && item.triageType !== request.triageType) {\n return this.#commitRejection(\n request,\n transitionId,\n 'forbidden',\n 'The persisted triage classification cannot be changed by a later transition.',\n );\n }\n const triageType = item.triageType ?? request.triageType;\n if (\n requiresHumanApproval(triageType) &&\n (request.stage === 'planning' || request.stage === 'execute') &&\n !isHumanTransition(request)\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.',\n );\n }\n\n // The card's own content can steer a bound agent; on a card authored\n // outside the write-access circle, leaving rest takes a person's gesture.\n if (\n request.actor.type === 'agent' &&\n !isWorkingFactoryRuleStage(fromStage) &&\n isWorkingFactoryRuleStage(request.stage) &&\n externallyAuthoredWorkItem(item)\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'This card comes from outside the write-access circle; a person must resume it from the Factory board.',\n );\n }\n\n const humanBoardDrag =\n request.actor.type === 'human' && request.cause === 'board_drag' && fromStage !== request.stage;\n\n const contextBase = {\n tenant: { orgId: request.orgId, projectId: request.factoryProjectId },\n actor: request.actor,\n ingress: { type: request.ingress.type, id: request.ingress.identity },\n cause: request.cause,\n causalChain: request.causalChain ?? [],\n ruleSetVersion: this.#rules.version,\n item: {\n id: item.id,\n source: itemSource,\n sourceKey: item.externalSource\n ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}`\n : null,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: [...item.stages],\n metadata: item.metadata,\n },\n board: request.board,\n itemRevision: item.revision,\n source,\n fromStage,\n toStage: request.stage,\n } satisfies Omit<FactoryStageRuleContext, 'stage'>;\n\n let evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string };\n try {\n evaluation = await withRuleTimeout(\n (async () => {\n const decisions: FactoryCommitDecision[] = [];\n for (const rule of resolveFactoryStageRules(this.#rules, {\n board: request.board,\n source,\n fromStage,\n toStage: request.stage,\n initialEntry: request.initialEntry,\n reenter: request.reenter,\n })) {\n const context: FactoryStageRuleContext = Object.freeze({\n ...contextBase,\n stage: rule.phase === 'exit' ? fromStage : request.stage,\n });\n const raw = await rule.handler(context);\n if (raw === undefined) continue;\n const decision = validateFactoryRuleDecision(raw, context.causalChain.length);\n if (decision.type === 'reject') {\n return { outcome: 'rejected' as const, code: decision.code, reason: decision.reason };\n }\n if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;\n decisions.push(decision);\n }\n const validated = validateFactoryRuleDecisions(decisions);\n if (humanBoardDrag) {\n const message = stageTransitionMessage(fromStage, request.stage);\n const skill = validated.find(decision => decision.type === 'invokeSkill');\n if (skill) {\n skill.precedingMessage = message;\n } else {\n validated.unshift({\n type: 'sendMessage',\n idempotencyKey: `factory-stage:${transitionId}`,\n message,\n priority: 'urgent',\n idleBehavior: 'wake',\n // Parking a card says stop: no seat is right by construction, so\n // the notice goes to whichever session is live — or nobody.\n ...(isWorkingFactoryRuleStage(request.stage)\n ? { role: roleForStage(request.board, request.stage), prepareBinding: true }\n : {}),\n });\n }\n }\n return {\n outcome: 'accepted' as const,\n decisions: validateFactoryRuleDecisions(validated) as unknown as Record<string, unknown>[],\n };\n })(),\n this.#timeoutMs,\n );\n } catch (error) {\n const failed =\n error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT'\n ? { code: 'timeout' as const, reason: 'Factory rule evaluation timed out.' }\n : ruleFailure(error);\n evaluation = { outcome: 'rejected', ...failed };\n }\n return this.#commit(\n request,\n transitionId,\n evaluation,\n evaluation.outcome === 'accepted' ? consentEffect(request, humanBoardDrag) : {},\n );\n }\n\n async #commitRejection(\n request: FactoryTransitionRequest,\n transitionId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n ): Promise<FactoryTransitionResult> {\n return this.#commit(request, transitionId, { outcome: 'rejected', code, reason });\n }\n\n async #commit(\n request: FactoryTransitionRequest,\n transitionId: string,\n evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string },\n options: TransitionConsentOptions = {},\n ): Promise<FactoryTransitionResult> {\n const committed = await this.#storage.commitTransition({\n autonomy: options.autonomy,\n consentedBy: options.consentedBy,\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n expectedRevision: request.expectedRevision,\n destinationStage: request.stage,\n actorId: actorId(request.actor),\n ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },\n ruleSetVersion: this.#rules.version,\n causalChain: [...(request.causalChain ?? [])],\n evaluation,\n ...(isTriageAgent(request.actor) && request.triageType ? { triageType: request.triageType } : {}),\n });\n if (committed.status === 'missing') {\n return rejection(transitionId, request.workItemId, 'invalid_transition', 'Work item not found.');\n }\n const result = committed.result as unknown as FactoryTransitionResult;\n if (this.#onTerminalStage && result.status === 'accepted' && TERMINAL_STAGES.has(result.stage)) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const cleanup = Promise.resolve(\n this.#onTerminalStage({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n stage: result.stage,\n }),\n );\n // A late rejection after the timeout wins the race must not surface\n // as an unhandled rejection.\n cleanup.catch(() => {});\n await Promise.race([\n cleanup,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, this.#terminalCleanupTimeoutMs);\n }),\n ]);\n } catch {\n // Resource release is best-effort — never fail a committed transition.\n } finally {\n clearTimeout(timer);\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transition-service.js","names":["#rules","#storage","#timeoutMs","#onTerminalStage","#terminalCleanupTimeoutMs","#onAccepted","#commitRejection","#commit"],"sources":["../../src/rules/transition-service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryStageRules } from './resolve.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleActor,\n FactoryRuleBoard,\n FactoryRuleCausalEntry,\n FactoryRuleRejectionCode,\n FactoryRuleStage,\n FactoryTriageType,\n FactoryRules,\n FactoryStageRuleContext,\n FactoryTransitionResult,\n} from './types.js';\nimport {\n externallyAuthoredWorkItem,\n factoryRuleSourceForWorkItem,\n isFactoryRuleStage,\n isWorkingFactoryRuleStage,\n workItemSource,\n} from './types.js';\nimport {\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n validateFactoryRuleDecision,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\nconst MAX_REJECTION_REASON = 512;\nconst TERMINAL_STAGES: ReadonlySet<FactoryRuleStage> = new Set(['done', 'canceled']);\n/** Longest a committed transition waits for terminal resource cleanup. Cleanup\n * reattaches remote sandboxes, so a hung provider call must not leave the\n * already-committed transition request pending; past this bound the cleanup\n * keeps running in the background as pure best-effort. */\nconst TERMINAL_CLEANUP_TIMEOUT_MS = 30_000;\n\nexport interface FactoryTransitionRequest {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n expectedRevision: number;\n actor: FactoryRuleActor;\n ingress: { type: 'human' | 'agent' | 'toolResult' | 'github' | 'rule'; identity: string; transitionId?: string };\n cause: string;\n causalChain?: readonly FactoryRuleCausalEntry[];\n /** Internal materialization path: evaluate only the destination onEnter leaf even when already at that stage. */\n initialEntry?: boolean;\n /** Re-runs the stage's entry rules when the item already holds that stage, to restart work the entry invalidated. */\n reenter?: boolean;\n /** Structured verdict required from a bound triage-agent terminal request. */\n triageType?: FactoryTriageType;\n}\n\nexport interface FactoryTransitionServiceOptions {\n rules: FactoryRules;\n storage: WorkItemsStorage;\n timeoutMs?: number;\n /**\n * Called after a transition commits into a terminal stage (`done` /\n * `canceled`) — the point where the item's sessions stop receiving runs, so\n * resources they hold (e.g. sandboxes) can be released for reuse. Awaited,\n * but failures are swallowed: releasing resources must never break or roll\n * back the committed transition.\n */\n onTerminalStage?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n stage: FactoryRuleStage;\n }) => Promise<void> | void;\n /** Upper bound on how long a committed transition waits for\n * `onTerminalStage` before returning (default 30s). The cleanup continues\n * in the background past the bound. */\n terminalCleanupTimeoutMs?: number;\n /**\n * Called after a transition first records a person's acceptance of a\n * non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures\n * are swallowed, the committed transition never depends on it.\n */\n onAccepted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n item: WorkItemRow;\n }) => Promise<void> | void;\n}\n\nfunction rejection(\n transitionId: string,\n itemId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n): FactoryTransitionResult {\n return { status: 'rejected', transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };\n}\n\nfunction actorId(actor: FactoryRuleActor): string {\n switch (actor.type) {\n case 'human':\n case 'system':\n return actor.id;\n case 'agent':\n return `agent:${actor.bindingId}`;\n case 'github':\n return `github:${actor.login}`;\n }\n}\n\nexport function currentStage(stages: readonly string[]): FactoryRuleStage | undefined {\n if (stages.length !== 1) return undefined;\n const stage = stages[0];\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function roleForStage(board: FactoryRuleBoard, stage: FactoryRuleStage): string {\n if (board === 'review') return 'review';\n if (stage === 'triage') return 'triage';\n if (stage === 'planning') return 'plan';\n return 'work';\n}\n\ninterface TransitionConsentOptions {\n autonomy?: 'arm' | 'disarm';\n consentedBy?: string;\n accept?: boolean;\n}\n\n// Entering a resting lane disarms whoever rests it; only a person's drag into a working lane arms.\nfunction transitionConsent(stage: FactoryRuleStage, humanBoardDrag: boolean): 'arm' | 'disarm' | undefined {\n if (!isWorkingFactoryRuleStage(stage)) return 'disarm';\n return humanBoardDrag ? 'arm' : undefined;\n}\n\n// An event arriving as data (GitHub, sweeps) never pre-approves the runs its transition queues.\nfunction bearsConsent(actor: FactoryRuleActor): boolean {\n return actor.type === 'human' || actor.type === 'agent';\n}\n\n// Rides the transition's own revision-checked commit, so a stale or rejected commit flips nothing.\nfunction consentEffect(request: FactoryTransitionRequest, humanBoardDrag: boolean): TransitionConsentOptions {\n const autonomy = transitionConsent(request.stage, humanBoardDrag);\n return bearsConsent(request.actor) ? { autonomy, consentedBy: actorId(request.actor) } : { autonomy };\n}\n\ntype RunStartDecision = Extract<FactoryCommitDecision, { type: 'invokeSkill' | 'sendMessage' }>;\n\nfunction startsRun(decision: FactoryCommitDecision): decision is RunStartDecision {\n return decision.type === 'invokeSkill' || (decision.type === 'sendMessage' && decision.prepareBinding === true);\n}\n\n// Answering a recorded run start, or the role's own mid-run agent, with a run would start a second one.\nfunction runAlreadyUnderway(request: FactoryTransitionRequest, decision: RunStartDecision): boolean {\n if (request.cause === 'run_start') return true;\n return request.actor.type === 'agent' && request.actor.role === decision.role;\n}\n\nfunction stageTransitionMessage(fromStage: FactoryRuleStage, toStage: FactoryRuleStage): string {\n return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;\n}\n\nfunction isTriageAgent(actor: FactoryRuleActor): actor is Extract<FactoryRuleActor, { type: 'agent' }> {\n return actor.type === 'agent' && actor.role === 'triage';\n}\n\nfunction isHumanTransition(request: FactoryTransitionRequest): boolean {\n return request.actor.type === 'human' && request.ingress.type === 'human';\n}\n\nfunction requiresHumanApproval(triageType: FactoryTriageType | null | undefined): boolean {\n return triageType !== undefined && triageType !== null && triageType !== 'bug';\n}\n\nfunction isAtRest(stage: FactoryRuleStage): boolean {\n return stage === 'intake' || stage === 'triage';\n}\n\nfunction entersWork(stage: FactoryRuleStage): boolean {\n return stage === 'planning' || stage === 'execute';\n}\n\n// A person moving a card into Planning/Execute is the approval gesture —\n// recorded once, so later agent hops need no second nod. Not limited to moves\n// out of rest: a card accepted before acceptance was recorded still gets its\n// stamp (and its label reconciled) the next time a person moves it forward.\nfunction acceptsItem(request: FactoryTransitionRequest): boolean {\n return isHumanTransition(request) && entersWork(request.stage);\n}\n\nfunction ruleFailure(error: unknown): { code: FactoryRuleRejectionCode; reason: string } {\n return {\n code: 'rule_error',\n reason: error instanceof Error ? `Factory rule failed: ${error.message}` : 'Factory rule failed.',\n };\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), timeoutMs);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryTransitionService {\n readonly #rules: FactoryRules;\n readonly #storage: WorkItemsStorage;\n readonly #timeoutMs: number;\n readonly #onTerminalStage: FactoryTransitionServiceOptions['onTerminalStage'];\n readonly #terminalCleanupTimeoutMs: number;\n readonly #onAccepted: FactoryTransitionServiceOptions['onAccepted'];\n\n constructor(options: FactoryTransitionServiceOptions) {\n this.#rules = options.rules;\n this.#storage = options.storage;\n this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;\n this.#onTerminalStage = options.onTerminalStage;\n this.#onAccepted = options.onAccepted;\n this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;\n }\n\n get ruleSetVersion(): string {\n return this.#rules.version;\n }\n\n async transition(request: FactoryTransitionRequest): Promise<FactoryTransitionResult> {\n const replay = await this.#storage.getTransitionResultByIngress(\n request.orgId,\n request.factoryProjectId,\n request.ingress.identity,\n );\n if (replay) return replay as unknown as FactoryTransitionResult;\n\n const transitionId = request.ingress.transitionId ?? randomUUID();\n const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });\n if (!item) {\n return this.#commitRejection(request, transitionId, 'invalid_transition', 'Work item not found.');\n }\n\n if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n return this.#commitRejection(\n request,\n transitionId,\n 'causal_depth_exceeded',\n 'Factory rule causal depth exceeded.',\n );\n }\n const itemSource = workItemSource(item.externalSource);\n const source = factoryRuleSourceForWorkItem(itemSource);\n if ((request.board === 'review') !== (source === 'pullRequest')) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not belong to the requested board.',\n );\n }\n const fromStage = currentStage(item.stages);\n if (!fromStage) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not have one canonical Factory stage.',\n );\n }\n\n if (isTriageAgent(request.actor) && request.triageType === undefined) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'Triage transitions must report a structured triage classification.',\n );\n }\n if (item.triageType && request.triageType && item.triageType !== request.triageType) {\n return this.#commitRejection(\n request,\n transitionId,\n 'forbidden',\n 'The persisted triage classification cannot be changed by a later transition.',\n );\n }\n // The gate stands at the exit of rest. A non-bug card already in\n // Planning/Execute can only have been put there by a person, so an agent\n // carrying it further (plan → build) is not asked for a second nod even\n // when the acceptance stamp predates its recording.\n const triageType = item.triageType ?? request.triageType;\n if (\n requiresHumanApproval(triageType) &&\n entersWork(request.stage) &&\n isAtRest(fromStage) &&\n !isHumanTransition(request) &&\n !item.acceptedAt\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.',\n );\n }\n\n // The card's own content can steer a bound agent; on a card authored\n // outside the write-access circle, leaving rest takes a person's gesture.\n if (\n request.actor.type === 'agent' &&\n !isWorkingFactoryRuleStage(fromStage) &&\n isWorkingFactoryRuleStage(request.stage) &&\n externallyAuthoredWorkItem(item)\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'This card comes from outside the write-access circle; a person must resume it from the Factory board.',\n );\n }\n\n const humanBoardDrag =\n request.actor.type === 'human' && request.cause === 'board_drag' && fromStage !== request.stage;\n\n const contextBase = {\n tenant: { orgId: request.orgId, projectId: request.factoryProjectId },\n actor: request.actor,\n ingress: { type: request.ingress.type, id: request.ingress.identity },\n cause: request.cause,\n causalChain: request.causalChain ?? [],\n ruleSetVersion: this.#rules.version,\n item: {\n id: item.id,\n source: itemSource,\n sourceKey: item.externalSource\n ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}`\n : null,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: [...item.stages],\n metadata: item.metadata,\n },\n board: request.board,\n itemRevision: item.revision,\n source,\n fromStage,\n toStage: request.stage,\n } satisfies Omit<FactoryStageRuleContext, 'stage'>;\n\n let evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string };\n try {\n evaluation = await withRuleTimeout(\n (async () => {\n const decisions: FactoryCommitDecision[] = [];\n for (const rule of resolveFactoryStageRules(this.#rules, {\n board: request.board,\n source,\n fromStage,\n toStage: request.stage,\n initialEntry: request.initialEntry,\n reenter: request.reenter,\n })) {\n const context: FactoryStageRuleContext = Object.freeze({\n ...contextBase,\n stage: rule.phase === 'exit' ? fromStage : request.stage,\n });\n const raw = await rule.handler(context);\n if (raw === undefined) continue;\n const decision = validateFactoryRuleDecision(raw, context.causalChain.length);\n if (decision.type === 'reject') {\n return { outcome: 'rejected' as const, code: decision.code, reason: decision.reason };\n }\n if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;\n decisions.push(decision);\n }\n const validated = validateFactoryRuleDecisions(decisions);\n if (humanBoardDrag) {\n const message = stageTransitionMessage(fromStage, request.stage);\n const skill = validated.find(decision => decision.type === 'invokeSkill');\n if (skill) {\n skill.precedingMessage = message;\n } else {\n validated.unshift({\n type: 'sendMessage',\n idempotencyKey: `factory-stage:${transitionId}`,\n message,\n priority: 'urgent',\n idleBehavior: 'wake',\n // Parking a card says stop: no seat is right by construction, so\n // the notice goes to whichever session is live — or nobody.\n ...(isWorkingFactoryRuleStage(request.stage)\n ? { role: roleForStage(request.board, request.stage), prepareBinding: true }\n : {}),\n });\n }\n }\n return {\n outcome: 'accepted' as const,\n decisions: validateFactoryRuleDecisions(validated) as unknown as Record<string, unknown>[],\n };\n })(),\n this.#timeoutMs,\n );\n } catch (error) {\n const failed =\n error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT'\n ? { code: 'timeout' as const, reason: 'Factory rule evaluation timed out.' }\n : ruleFailure(error);\n evaluation = { outcome: 'rejected', ...failed };\n }\n return this.#commit(\n request,\n transitionId,\n evaluation,\n evaluation.outcome === 'accepted'\n ? { ...consentEffect(request, humanBoardDrag), accept: acceptsItem(request) && !item.acceptedAt }\n : {},\n );\n }\n\n async #commitRejection(\n request: FactoryTransitionRequest,\n transitionId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n ): Promise<FactoryTransitionResult> {\n return this.#commit(request, transitionId, { outcome: 'rejected', code, reason });\n }\n\n async #commit(\n request: FactoryTransitionRequest,\n transitionId: string,\n evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string },\n options: TransitionConsentOptions = {},\n ): Promise<FactoryTransitionResult> {\n const committed = await this.#storage.commitTransition({\n autonomy: options.autonomy,\n consentedBy: options.consentedBy,\n ...(options.accept ? { accept: true } : {}),\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n expectedRevision: request.expectedRevision,\n destinationStage: request.stage,\n actorId: actorId(request.actor),\n ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },\n ruleSetVersion: this.#rules.version,\n causalChain: [...(request.causalChain ?? [])],\n evaluation,\n ...(isTriageAgent(request.actor) && request.triageType ? { triageType: request.triageType } : {}),\n });\n if (committed.status === 'missing') {\n return rejection(transitionId, request.workItemId, 'invalid_transition', 'Work item not found.');\n }\n const result = committed.result as unknown as FactoryTransitionResult;\n if (\n this.#onAccepted &&\n options.accept &&\n committed.status === 'committed' &&\n result.status === 'accepted' &&\n committed.item?.acceptedAt\n ) {\n const item = committed.item;\n void Promise.resolve(\n this.#onAccepted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n item,\n }),\n ).catch(error => {\n console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);\n });\n }\n if (this.#onTerminalStage && result.status === 'accepted' && TERMINAL_STAGES.has(result.stage)) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const cleanup = Promise.resolve(\n this.#onTerminalStage({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n stage: result.stage,\n }),\n );\n // A late rejection after the timeout wins the race must not surface\n // as an unhandled rejection.\n cleanup.catch(() => {});\n await Promise.race([\n cleanup,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, this.#terminalCleanupTimeoutMs);\n }),\n ]);\n } catch {\n // Resource release is best-effort — never fail a committed transition.\n } finally {\n clearTimeout(timer);\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-sandbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAG3E;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,SAAS,CAAC;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,GAAG,EAAE,qBAAqB,KAAK,aAAa,CAAC;AAEvF;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpH;;;;;;;;;GASG;AACH,UAAU,mBAAmB;IAC3B,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,gBAAgB,GAChC,mBAAmB,CAQrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAErF;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3D;AAED,4DAA4D;AAC5D,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD;AAcD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjF;AAqCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,eAAe,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,gBAAgB,CAkBlB"}
1
+ {"version":3,"file":"session-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-sandbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAI3E;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,SAAS,CAAC;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,GAAG,EAAE,qBAAqB,KAAK,aAAa,CAAC;AAEvF;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpH;;;;;;;;;GASG;AACH,UAAU,mBAAmB;IAC3B,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,gBAAgB,GAChC,mBAAmB,CAQrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAErF;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3D;AAED,4DAA4D;AAC5D,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD;AAcD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjF;AAqCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,eAAe,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,gBAAgB,CAyBlB"}
@@ -1,3 +1,4 @@
1
+ import { timedPhase } from "../timing.js";
1
2
  import { SETUP_MARKER_PATH, setupMarkerContent } from "../packages/_internals/workspace/dist/index.js";
2
3
  import { deriveLocalWorkdir, deriveRemoteRepoDir, repoDirUnder } from "./workdir.js";
3
4
  import path from "path";
@@ -116,8 +117,10 @@ function createSessionSetupHook(run, sessionId, repoFullName, setupCommand) {
116
117
  return async ({ sandbox }) => {
117
118
  if (!sandbox.executeCommand) throw new Error(`Sandbox '${sandbox.id}' cannot run the session setup: no executeCommand implementation`);
118
119
  const workdir = await resolveSessionWorkdir(sessionId, sandbox, repoFullName);
120
+ const setupDone = marker ? await timedPhase("workspace.setup-marker", () => markerMatches(sandbox, workdir, marker)) : true;
121
+ if (marker) process.stderr.write(`[factory:setup] ${setupDone ? "marker matches, skipping setup command" : "no matching marker, setup command will run"} (${markerShellPath(workdir)})\n`);
119
122
  await run(sandbox, workdir, {
120
- setupDone: marker ? await markerMatches(sandbox, workdir, marker) : true,
123
+ setupDone,
121
124
  markSetupDone: () => marker ? writeMarker(sandbox, workdir, marker) : Promise.resolve()
122
125
  });
123
126
  };