@mastra/factory 0.26.0-alpha.1 → 0.26.0-alpha.3
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.
- package/README.md +74 -7
- package/dist/boards/define-board.d.ts +19 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +41 -9
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/project-boards.d.ts +78 -0
- package/dist/boards/project-boards.d.ts.map +1 -0
- package/dist/boards/project-boards.js +194 -0
- package/dist/boards/project-boards.js.map +1 -0
- package/dist/boards/review.js +2 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/stored-board.d.ts +101 -0
- package/dist/boards/stored-board.d.ts.map +1 -0
- package/dist/boards/stored-board.js +139 -0
- package/dist/boards/stored-board.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +6 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +6 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/environment/build.d.ts +1 -1
- package/dist/environment/build.d.ts.map +1 -1
- package/dist/environment/build.js +2 -0
- package/dist/environment/build.js.map +1 -1
- package/dist/factory.d.ts +15 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +51 -12
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/integration.d.ts +5 -4
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +10 -8
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -2
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +6 -1
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/jira/binding-scope.d.ts +30 -0
- package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
- package/dist/integrations/jira/binding-scope.js +50 -0
- package/dist/integrations/jira/binding-scope.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +12 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -1
- package/dist/integrations/jira/claim.js +24 -1
- package/dist/integrations/jira/claim.js.map +1 -1
- package/dist/integrations/jira/intake-jql.d.ts +32 -0
- package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
- package/dist/integrations/jira/intake-jql.js +57 -0
- package/dist/integrations/jira/intake-jql.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +4 -3
- package/dist/integrations/jira/integration.d.ts.map +1 -1
- package/dist/integrations/jira/integration.js +14 -21
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/routes.d.ts +3 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -1
- package/dist/integrations/jira/routes.js +16 -29
- package/dist/integrations/jira/routes.js.map +1 -1
- package/dist/integrations/jira/rules.d.ts.map +1 -1
- package/dist/integrations/jira/rules.js +8 -13
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +5 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +7 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +7 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js +26 -24
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/jira/routes.d.ts +3 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
- package/dist/integrations/platform/jira/routes.js +17 -28
- package/dist/integrations/platform/jira/routes.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +2 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +3 -2
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts +2 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/boards.d.ts +30 -0
- package/dist/routes/boards.d.ts.map +1 -0
- package/dist/routes/boards.js +272 -0
- package/dist/routes/boards.js.map +1 -0
- package/dist/routes/contracts.d.ts +249 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +107 -35
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/intake.d.ts +15 -10
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +109 -22
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/surface.d.ts +10 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +13 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +9 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +24 -2
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +64 -42
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +4 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +7 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +70 -34
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +3 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +11 -6
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +2 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +3 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +12 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts +4 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -1
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +6 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/boards/base.d.ts +110 -0
- package/dist/storage/domains/boards/base.d.ts.map +1 -0
- package/dist/storage/domains/boards/base.js +292 -0
- package/dist/storage/domains/boards/base.js.map +1 -0
- package/dist/storage/domains/boards/schemas.d.ts +11 -0
- package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
- package/dist/storage/domains/boards/schemas.js +106 -0
- package/dist/storage/domains/boards/schemas.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +4 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +5 -2
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +86 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +298 -59
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +2 -1
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +15 -10
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +16 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +40 -11
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/read-tools.d.ts +3 -1
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +3 -2
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/session-tools.d.ts +3 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +2 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.d.ts +3 -1
- package/dist/tools/sessions-state-processor.d.ts.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -1
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/work-item-tools.d.ts +3 -1
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +3 -2
- package/dist/tools/work-item-tools.js.map +1 -1
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +14 -5
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-plan/SKILL.md +1 -1
- package/factory-skills/factory-triage/SKILL.md +18 -7
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-items.js","names":[],"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 { createBoardRegistry } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { EXTERNAL_SOURCE_MISSING_KEY } from '../integrations/issue-reconciler.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport type {\n FactoryStartCoordinator,\n FactoryStartPreparedResult,\n FactoryStartRequest,\n} from '../rules/start-coordinator.js';\nimport { FactoryStartTransitionError, WorkItemRepositoryError } from '../rules/start-coordinator.js';\nimport type { FactoryTransitionRequest, FactoryTransitionService } from '../rules/transition-service.js';\nimport type { WorkItemSource } from '../rules/types.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport { auditRequestOrigin } from '../storage/domains/audit/domain.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, 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 FactoryDeferredDecisionRecord,\n UpdateWorkItemInput,\n WorkItemRow,\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 WorkItemUpdateConflictError,\n} from '../storage/domains/work-items/base.js';\nimport { computeFactoryMetrics, parseMetricsRange } from '../storage/domains/work-items/metrics.js';\nimport { createFactoryWorkItem } from '../work-items/create-work-item.js';\nimport { resolveFactoryProposal, retryFactoryDecision } from '../work-items/decision-actions.js';\nimport { buildAttentionRoutes, factoryDecisionType } from './attention.js';\nimport { FACTORY_ROUTE_CONTRACTS } from './contracts.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 /** Boards installed for this Factory instance. */\n boardRegistry?: BoardRegistry;\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' | 'configVersion'>;\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 or wait on someone. */\n liveSessions: Pick<LiveSessions, 'isRunning' | 'parked' | 'parkedIn'>;\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 !(EXTERNAL_SOURCE_MISSING_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 that the predicate picks, each once. */\nfunction sessionIdsWhere(items: WorkItemRow[], predicate: (sessionId: string) => boolean): string[] {\n const picked = new Set<string>();\n for (const item of items) {\n for (const { sessionId } of Object.values(item.sessions)) {\n if (predicate(sessionId)) picked.add(sessionId);\n }\n }\n return [...picked];\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\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 [EXTERNAL_SOURCE_MISSING_KEY]: _externalSourceMissingAt,\n ...metadata\n } = value;\n return metadata;\n}\n\n/** Validate an untrusted create body. Unknown keys are dropped. */\nexport function parseCreateWorkItem(body: unknown): CreateWorkItemInput | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemCreate.bodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\n/** Validate an untrusted patch body. Unknown keys are dropped. */\nexport function parseUpdateWorkItem(body: unknown): UpdateWorkItemInput | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemUpdate.bodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\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 parseTransitionBody(\n body: unknown,\n): Omit<FactoryTransitionRequest, 'orgId' | 'factoryProjectId' | 'workItemId' | 'actor'> | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemTransition.bodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\nfunction parseStartBody(\n body: unknown,\n tenant: { orgId: string; userId: string },\n factoryProjectId: string,\n modelScope: EffectiveModelScope,\n): FactoryStartRequest | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemStart.bodySchema.safeParse(body);\n return parsed.success ? { ...tenant, factoryProjectId, modelScope, ...parsed.data } : null;\n}\n\nfunction encodeDecisionCursor(decision: FactoryDeferredDecisionRecord): string {\n return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), 'utf8').toString('base64url');\n}\n\n/** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */\nfunction summaryRole(boards: BoardRegistry, 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, stage } = decision;\n if (typeof board !== 'string' || typeof stage !== 'string') return null;\n return boards.get(board)?.roleForPhase(stage) ?? null;\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' ||\n source === 'github-pr' ||\n source === 'gitlab-issue' ||\n source === 'gitlab-pr' ||\n source === 'linear-issue' ||\n source === 'manual'\n ? source\n : null;\n}\n\nfunction decisionSummary(boards: BoardRegistry, decision: FactoryDeferredDecisionRecord, overtaken = false) {\n return {\n id: decision.id,\n evaluationId: decision.evaluationId,\n workItemId: decision.workItemId,\n type: factoryDecisionType(decision),\n role: summaryRole(boards, 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: decisionRetryable(decision, overtaken),\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 #defaultBoards: BoardRegistry | undefined;\n\n get #boards(): BoardRegistry {\n return this.deps.boardRegistry ?? (this.#defaultBoards ??= createBoardRegistry());\n }\n\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(c: Context): Promise<\n | {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n modelScope: EffectiveModelScope;\n defaultModelId: string | null;\n }\n | { response: Response }\n > {\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant;\n\n const parsedPath = FACTORY_ROUTE_CONTRACTS.projectGet.pathSchema.safeParse({ id: c.req.param('id') });\n if (!parsedPath.success) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const projectId = parsedPath.data.id;\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 // Work-item starts are runs, so a hybrid Factory resolves the Factory's scope here.\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n return {\n ...tenant,\n factoryProjectId: projectId,\n modelScope,\n defaultModelId: modelScope === 'factory' ? project.defaultModelId : null,\n };\n }\n\n async #auditWorkItemPatch({\n context,\n item,\n patch,\n }: {\n context: Context;\n item: WorkItemRow;\n patch: Record<string, unknown>;\n }): Promise<void> {\n await this.deps.audit.emit({\n context,\n input: {\n action: 'factory.work_item.updated',\n factoryProjectId: item.factoryProjectId,\n targets: [{ type: 'work_item', id: item.id, name: item.title }],\n metadata: { fields: patchedFields(patch) },\n },\n });\n }\n\n /** Releasing a parked run and dropping it: same request, opposite outcomes, both audited as consent. */\n #proposalRoute({ verb }: { verb: 'approve' | 'dismiss' }): ApiRoute {\n const { audit, workItems } = this.deps;\n const contract =\n verb === 'approve' ? FACTORY_ROUTE_CONTRACTS.decisionApprove : FACTORY_ROUTE_CONTRACTS.decisionDismiss;\n return registerApiRoute(contract.path, {\n method: contract.method,\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 parsedPath = contract.pathSchema.safeParse({\n id: resolved.factoryProjectId,\n decisionId: context.req.param('decisionId'),\n });\n if (!parsedPath.success) return c.json({ error: 'invalid_decision_id' }, 422);\n const { decisionId } = parsedPath.data;\n const settled = await resolveFactoryProposal({\n workItems,\n audit: input => audit.emit({ context, input }),\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n decisionId,\n action: verb,\n });\n if (!settled.ok) return c.json({ error: settled.code }, 409);\n const { decision } = settled;\n return c.json({ decision: decisionSummary(this.#boards, 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 registerApiRoute(FACTORY_ROUTE_CONTRACTS.boardCatalog.path, {\n method: FACTORY_ROUTE_CONTRACTS.boardCatalog.method,\n requiresAuth: false,\n handler: async c => {\n const resolved = await this.#resolveProject(loose(c));\n if ('response' in resolved) return resolved.response;\n return c.json({\n boards: [...this.#boards].map(([id, board]) => ({\n id,\n name: board.name,\n title: board.name,\n ...(board.description !== undefined ? { description: board.description } : {}),\n initialPhase: board.initialPhase,\n phases: Object.entries(board.phases).map(([phaseId, phase]) => ({\n id: phaseId,\n name: phase.name,\n title: phase.name,\n ...(phase.description !== undefined ? { description: phase.description } : {}),\n kind: phase.kind,\n nextPhases: [...phase.nextPhases],\n ...(phase.kind === 'working'\n ? {\n role: phase.runner.context,\n context: phase.runner.context,\n ...(phase.runner.skill ? { skill: phase.runner.skill } : {}),\n autoStart: phase.autoStart === true,\n ...(phase.autoStartWhen ? { autoStartWhen: phase.autoStartWhen } : {}),\n ...(phase.exitTool\n ? {\n exitTool: {\n id: phase.exitTool.id,\n autoApprove: phase.exitTool.autoApprove,\n ...(phase.exitTool.autoApproveWhen\n ? { autoApproveWhen: phase.exitTool.autoApproveWhen }\n : {}),\n },\n }\n : {}),\n }\n : {}),\n transitions: (board.transitions[phaseId] ?? []).map(({ outcome, to }) => ({ outcome, to })),\n })),\n })),\n });\n },\n }),\n // ── List the org's work items for a project, and which are being worked ─\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemList.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemList.method,\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: sessionIdsWhere(items, sessionId => liveSessions.isRunning(sessionId)),\n parkedSessionIds: sessionIdsWhere(items, sessionId => liveSessions.parked(sessionId) !== undefined),\n });\n },\n }),\n\n // ── Flow metrics aggregated over the project's work items ───────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.metricsGet.path, {\n method: FACTORY_ROUTE_CONTRACTS.metricsGet.method,\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 query = FACTORY_ROUTE_CONTRACTS.metricsGet.querySchema.safeParse({\n from: context.req.query('from'),\n to: context.req.query('to'),\n });\n if (!query.success) return c.json({ error: 'invalid_metrics_range' }, 400);\n const { windowStart, windowEnd } = parseMetricsRange(query.data.from, query.data.to, new Date());\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(FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.path, {\n method: FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.method,\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(FACTORY_ROUTE_CONTRACTS.decisionList.path, {\n method: FACTORY_ROUTE_CONTRACTS.decisionList.method,\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 query = FACTORY_ROUTE_CONTRACTS.decisionList.querySchema.safeParse({\n statuses: context.req.query('statuses'),\n before: context.req.query('before'),\n limit: context.req.query('limit'),\n });\n if (!query.success) {\n const field = query.error.issues[0]?.path[0];\n return c.json({ error: field === 'before' ? 'invalid_cursor' : 'invalid_decision_query' }, 400);\n }\n await workItems.ensureReady();\n const page = await workItems.listDeferredDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: query.data.statuses,\n before: query.data.before,\n limit: query.data.limit,\n });\n const last = page.decisions.at(-1);\n const overtaken = await overtakenDecisionIds(\n workItems,\n this.#boards,\n resolved,\n page.decisions.filter(decision => decision.status === 'failed'),\n );\n return c.json({\n decisions: page.decisions.map(decision =>\n decisionSummary(this.#boards, decision, overtaken.has(decision.id)),\n ),\n ...(page.hasMore && last ? { nextCursor: encodeDecisionCursor(last) } : {}),\n });\n },\n }),\n\n ...buildAttentionRoutes({\n workItems,\n boards: this.#boards,\n comments: this.deps.comments,\n liveSessions,\n resolveProject: context => this.#resolveProject(loose(context)),\n }),\n\n this.#proposalRoute({ verb: 'approve' }),\n this.#proposalRoute({ verb: 'dismiss' }),\n\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.decisionRetry.path, {\n method: FACTORY_ROUTE_CONTRACTS.decisionRetry.method,\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 parsedPath = FACTORY_ROUTE_CONTRACTS.decisionRetry.pathSchema.safeParse({\n id: resolved.factoryProjectId,\n decisionId: context.req.param('decisionId'),\n });\n if (!parsedPath.success) return c.json({ error: 'invalid_decision_id' }, 422);\n const { decisionId } = parsedPath.data;\n const retried = await retryFactoryDecision({\n workItems,\n boards: this.#boards,\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n decisionId,\n });\n if (!retried.ok) return c.json({ error: retried.code }, 409);\n const { decision } = retried;\n return c.json({ decision: decisionSummary(this.#boards, decision) });\n },\n }),\n\n // ── Create (upsert on sourceKey) a work item ─────────────────────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemCreate.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemCreate.method,\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 try {\n const created = await createFactoryWorkItem({\n workItems,\n boards: this.#boards,\n transitionService,\n audit: auditInput => audit.emit({ context: loose(c), input: auditInput }),\n orgId: resolved.orgId,\n userId: resolved.userId,\n factoryProjectId: resolved.factoryProjectId,\n input,\n origin: auditRequestOrigin(loose(c)),\n });\n switch (created.status) {\n case 'refused':\n if (created.code === 'factory_transitions_unavailable') return c.json({ error: created.code }, 503);\n return c.json(\n { error: created.code, message: created.message },\n created.code === 'invalid_board' ? 422 : 409,\n );\n case 'rejected':\n return c.json({ status: 'rejected', code: created.code, reason: created.reason }, 422);\n case 'reused': {\n // Source-key reuse: the POST updated an existing card, not created one.\n const { stages: _stages, sessions: _sessions, ...boundedPatch } = input;\n await this.#auditWorkItemPatch({\n context: loose(c),\n item: created.item,\n patch: boundedPatch as unknown as Record<string, unknown>,\n });\n return c.json({ workItem: toWireWorkItem(created.item) });\n }\n case 'created':\n return c.json({ workItem: toWireWorkItem(created.item) });\n }\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(FACTORY_ROUTE_CONTRACTS.workItemTransition.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemTransition.method,\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 context = loose(c);\n const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemTransition.pathSchema.safeParse({\n id: resolved.factoryProjectId,\n workItemId: context.req.param('workItemId'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n const { workItemId } = parsedPath.data;\n const parsed = parseTransitionBody(await readJson(context));\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 ...auditRequestOrigin(loose(c)),\n ingress: {\n ...parsed.ingress,\n identity: `human:${resolved.userId}:${parsed.ingress.identity}`,\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(FACTORY_ROUTE_CONTRACTS.workItemStart.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemStart.method,\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(\n await readJson(loose(c)),\n resolved,\n resolved.factoryProjectId,\n resolved.modelScope,\n );\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 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 if (error instanceof WorkItemRepositoryError) {\n return c.json({ error: error.code, message: error.message, candidates: error.candidates }, 409);\n }\n throw error;\n }\n return c.json({ prepared }, 202);\n },\n }),\n\n // ── Patch non-stage metadata / sessions / title ──────────────────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemUpdate.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemUpdate.method,\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 context = loose(c);\n const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemUpdate.pathSchema.safeParse({\n id: context.req.param('id'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n const { id } = parsedPath.data;\n\n const body = await readJson(context);\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\n await workItems.ensureReady();\n const existing = await workItems.get({ orgId: tenant.orgId, id });\n if (!existing) return c.json({ error: 'Work item not found' }, 404);\n if (patch.board !== undefined) {\n const board = this.#boards.get(patch.board);\n const stage = patch.stages?.length === 1 ? patch.stages[0] : undefined;\n if (existing.board !== null) {\n return c.json({ error: 'board_already_assigned' }, 409);\n }\n if (!board) {\n return c.json({ error: 'board_not_installed' }, 400);\n }\n if (!stage || !Object.prototype.hasOwnProperty.call(board.phases, stage)) {\n return c.json({ error: 'invalid_board_migration_phase' }, 400);\n }\n } else 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 try {\n const updated = await workItems.update({\n orgId: tenant.orgId,\n id,\n userId: tenant.userId,\n patch,\n ...(patch.board !== undefined ? { expectedRevision: existing.revision, expectedBoard: null } : {}),\n });\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 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 if (error instanceof WorkItemUpdateConflictError) {\n return c.json({ error: error.reason === 'board' ? 'board_already_assigned' : error.code }, 409);\n }\n throw error;\n }\n },\n }),\n\n // ── Remove a work item ───────────────────────────────────────────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemDelete.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemDelete.method,\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 context = loose(c);\n const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemDelete.pathSchema.safeParse({\n id: context.req.param('id'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n const { id } = parsedPath.data;\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":[],"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 type { BoardRegistry } from '../boards/index.js';\nimport { toBoardSource } from '../boards/project-boards.js';\nimport type { ProjectBoardSource, StoredBoardDetails } from '../boards/project-boards.js';\nimport { EXTERNAL_SOURCE_MISSING_KEY } from '../integrations/issue-reconciler.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport type {\n FactoryStartCoordinator,\n FactoryStartPreparedResult,\n FactoryStartRequest,\n} from '../rules/start-coordinator.js';\nimport { FactoryStartTransitionError, WorkItemRepositoryError } from '../rules/start-coordinator.js';\nimport type { FactoryTransitionRequest, FactoryTransitionService } from '../rules/transition-service.js';\nimport type { WorkItemSource } from '../rules/types.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport { auditRequestOrigin } from '../storage/domains/audit/domain.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, 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 FactoryDeferredDecisionRecord,\n UpdateWorkItemInput,\n WorkItemRow,\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 WorkItemUpdateConflictError,\n} from '../storage/domains/work-items/base.js';\nimport { computeFactoryMetrics, parseMetricsRange } from '../storage/domains/work-items/metrics.js';\nimport { createFactoryWorkItem } from '../work-items/create-work-item.js';\nimport { resolveFactoryProposal, retryFactoryDecision } from '../work-items/decision-actions.js';\nimport { buildAttentionRoutes, factoryDecisionType } from './attention.js';\nimport { FACTORY_ROUTE_CONTRACTS } from './contracts.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 /** Boards installed for this Factory instance. */\n /** A fixed registry, or the per-project boards. Defaults to the built-in boards. */\n boardRegistry?: BoardRegistry | ProjectBoardSource;\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' | 'configVersion'>;\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 or wait on someone. */\n liveSessions: Pick<LiveSessions, 'isRunning' | 'parked' | 'parkedIn'>;\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 !(EXTERNAL_SOURCE_MISSING_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 that the predicate picks, each once. */\nfunction sessionIdsWhere(items: readonly WorkItemRow[], predicate: (sessionId: string) => boolean): string[] {\n const picked = new Set<string>();\n for (const item of items) {\n for (const { sessionId } of Object.values(item.sessions)) {\n if (predicate(sessionId)) picked.add(sessionId);\n }\n }\n return [...picked];\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\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 [EXTERNAL_SOURCE_MISSING_KEY]: _externalSourceMissingAt,\n ...metadata\n } = value;\n return metadata;\n}\n\n/** Validate an untrusted create body. Unknown keys are dropped. */\nexport function parseCreateWorkItem(body: unknown): CreateWorkItemInput | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemCreate.bodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\n/** Validate an untrusted patch body. Unknown keys are dropped. */\nexport function parseUpdateWorkItem(body: unknown): UpdateWorkItemInput | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemUpdate.bodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\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 parseTransitionBody(\n body: unknown,\n): Omit<FactoryTransitionRequest, 'orgId' | 'factoryProjectId' | 'workItemId' | 'actor'> | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemTransition.bodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\nfunction parseStartBody(\n body: unknown,\n tenant: { orgId: string; userId: string },\n factoryProjectId: string,\n modelScope: EffectiveModelScope,\n): FactoryStartRequest | null {\n const parsed = FACTORY_ROUTE_CONTRACTS.workItemStart.bodySchema.safeParse(body);\n return parsed.success ? { ...tenant, factoryProjectId, modelScope, ...parsed.data } : null;\n}\n\nfunction encodeDecisionCursor(decision: FactoryDeferredDecisionRecord): string {\n return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), 'utf8').toString('base64url');\n}\n\n/** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */\nfunction summaryRole(boards: BoardRegistry, 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, stage } = decision;\n if (typeof board !== 'string' || typeof stage !== 'string') return null;\n return boards.get(board)?.roleForPhase(stage) ?? null;\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' ||\n source === 'github-pr' ||\n source === 'gitlab-issue' ||\n source === 'gitlab-pr' ||\n source === 'linear-issue' ||\n source === 'manual'\n ? source\n : null;\n}\n\nfunction decisionSummary(boards: BoardRegistry, decision: FactoryDeferredDecisionRecord, overtaken = false) {\n return {\n id: decision.id,\n evaluationId: decision.evaluationId,\n workItemId: decision.workItemId,\n type: factoryDecisionType(decision),\n role: summaryRole(boards, 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: decisionRetryable(decision, overtaken),\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\n/** Where a catalog board came from, whether it can be edited, and what is wrong with it. */\nfunction catalogDetails(details: StoredBoardDetails | undefined) {\n if (!details) return {};\n return {\n source: details.source,\n editable: details.editable,\n ...(details.problems.length > 0 ? { problems: details.problems } : {}),\n };\n}\n\nexport class WorkItemRoutes extends Route<WorkItemRoutesDeps> {\n #source: ProjectBoardSource | undefined;\n\n get #boards(): ProjectBoardSource {\n return (this.#source ??= toBoardSource(this.deps.boardRegistry));\n }\n\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(c: Context): Promise<\n | {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n modelScope: EffectiveModelScope;\n defaultModelId: string | null;\n }\n | { response: Response }\n > {\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant;\n\n const parsedPath = FACTORY_ROUTE_CONTRACTS.projectGet.pathSchema.safeParse({ id: c.req.param('id') });\n if (!parsedPath.success) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const projectId = parsedPath.data.id;\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 // Work-item starts are runs, so a hybrid Factory resolves the Factory's scope here.\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n return {\n ...tenant,\n factoryProjectId: projectId,\n modelScope,\n defaultModelId: modelScope === 'factory' ? project.defaultModelId : null,\n };\n }\n\n async #auditWorkItemPatch({\n context,\n item,\n patch,\n }: {\n context: Context;\n item: WorkItemRow;\n patch: Record<string, unknown>;\n }): Promise<void> {\n await this.deps.audit.emit({\n context,\n input: {\n action: 'factory.work_item.updated',\n factoryProjectId: item.factoryProjectId,\n targets: [{ type: 'work_item', id: item.id, name: item.title }],\n metadata: { fields: patchedFields(patch) },\n },\n });\n }\n\n /** Releasing a parked run and dropping it: same request, opposite outcomes, both audited as consent. */\n #proposalRoute({ verb }: { verb: 'approve' | 'dismiss' }): ApiRoute {\n const { audit, workItems } = this.deps;\n const contract =\n verb === 'approve' ? FACTORY_ROUTE_CONTRACTS.decisionApprove : FACTORY_ROUTE_CONTRACTS.decisionDismiss;\n return registerApiRoute(contract.path, {\n method: contract.method,\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 parsedPath = contract.pathSchema.safeParse({\n id: resolved.factoryProjectId,\n decisionId: context.req.param('decisionId'),\n });\n if (!parsedPath.success) return c.json({ error: 'invalid_decision_id' }, 422);\n const { decisionId } = parsedPath.data;\n const settled = await resolveFactoryProposal({\n workItems,\n audit: input => audit.emit({ context, input }),\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n decisionId,\n action: verb,\n });\n if (!settled.ok) return c.json({ error: settled.code }, 409);\n const { decision } = settled;\n return c.json({ decision: decisionSummary(await this.#boards.load(resolved), 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 registerApiRoute(FACTORY_ROUTE_CONTRACTS.boardCatalog.path, {\n method: FACTORY_ROUTE_CONTRACTS.boardCatalog.method,\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 project = this.#boards.describe\n ? await this.#boards.describe(resolved)\n : { registry: await this.#boards.load(resolved), details: undefined, problems: [] };\n return c.json({\n boards: [...project.registry].map(([id, board]) => ({\n id,\n ...catalogDetails(project.details?.get(id)),\n name: board.name,\n title: board.name,\n ...(board.description !== undefined ? { description: board.description } : {}),\n initialPhase: board.initialPhase,\n phases: Object.entries(board.phases).map(([phaseId, phase]) => ({\n id: phaseId,\n name: phase.name,\n title: phase.name,\n ...(phase.description !== undefined ? { description: phase.description } : {}),\n kind: phase.kind,\n nextPhases: [...phase.nextPhases],\n ...(phase.kind === 'working'\n ? {\n role: phase.runner.context,\n context: phase.runner.context,\n ...(phase.runner.skill ? { skill: phase.runner.skill } : {}),\n autoStart: phase.autoStart === true,\n ...(phase.autoStartWhen ? { autoStartWhen: phase.autoStartWhen } : {}),\n ...(phase.exitTool\n ? {\n exitTool: {\n id: phase.exitTool.id,\n autoApprove: phase.exitTool.autoApprove,\n ...(phase.exitTool.autoApproveWhen\n ? { autoApproveWhen: phase.exitTool.autoApproveWhen }\n : {}),\n },\n }\n : {}),\n }\n : {}),\n transitions: (board.transitions[phaseId] ?? []).map(({ outcome, to }) => ({ outcome, to })),\n })),\n })),\n ...(project.problems.length > 0 ? { problems: [...project.problems] } : {}),\n });\n },\n }),\n // ── List the org's work items for a project, and which are being worked ─\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemList.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemList.method,\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.listShared({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n });\n return c.json({\n workItems: items.map(toWireWorkItem),\n runningSessionIds: sessionIdsWhere(items, sessionId => liveSessions.isRunning(sessionId)),\n parkedSessionIds: sessionIdsWhere(items, sessionId => liveSessions.parked(sessionId) !== undefined),\n });\n },\n }),\n\n // ── Flow metrics aggregated over the project's work items ───────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.metricsGet.path, {\n method: FACTORY_ROUTE_CONTRACTS.metricsGet.method,\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 query = FACTORY_ROUTE_CONTRACTS.metricsGet.querySchema.safeParse({\n from: context.req.query('from'),\n to: context.req.query('to'),\n });\n if (!query.success) return c.json({ error: 'invalid_metrics_range' }, 400);\n const { windowStart, windowEnd } = parseMetricsRange(query.data.from, query.data.to, new Date());\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(FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.path, {\n method: FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.method,\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(FACTORY_ROUTE_CONTRACTS.decisionList.path, {\n method: FACTORY_ROUTE_CONTRACTS.decisionList.method,\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 query = FACTORY_ROUTE_CONTRACTS.decisionList.querySchema.safeParse({\n statuses: context.req.query('statuses'),\n before: context.req.query('before'),\n limit: context.req.query('limit'),\n });\n if (!query.success) {\n const field = query.error.issues[0]?.path[0];\n return c.json({ error: field === 'before' ? 'invalid_cursor' : 'invalid_decision_query' }, 400);\n }\n await workItems.ensureReady();\n const page = await workItems.listDeferredDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: query.data.statuses,\n before: query.data.before,\n limit: query.data.limit,\n });\n const last = page.decisions.at(-1);\n const boards = await this.#boards.load(resolved);\n const overtaken = await overtakenDecisionIds(\n workItems,\n boards,\n resolved,\n page.decisions.filter(decision => decision.status === 'failed'),\n );\n return c.json({\n decisions: page.decisions.map(decision => decisionSummary(boards, decision, overtaken.has(decision.id))),\n ...(page.hasMore && last ? { nextCursor: encodeDecisionCursor(last) } : {}),\n });\n },\n }),\n\n ...buildAttentionRoutes({\n workItems,\n boards: this.#boards,\n comments: this.deps.comments,\n liveSessions,\n resolveProject: context => this.#resolveProject(loose(context)),\n }),\n\n this.#proposalRoute({ verb: 'approve' }),\n this.#proposalRoute({ verb: 'dismiss' }),\n\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.decisionRetry.path, {\n method: FACTORY_ROUTE_CONTRACTS.decisionRetry.method,\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 parsedPath = FACTORY_ROUTE_CONTRACTS.decisionRetry.pathSchema.safeParse({\n id: resolved.factoryProjectId,\n decisionId: context.req.param('decisionId'),\n });\n if (!parsedPath.success) return c.json({ error: 'invalid_decision_id' }, 422);\n const { decisionId } = parsedPath.data;\n const boards = await this.#boards.load(resolved);\n const retried = await retryFactoryDecision({\n workItems,\n boards,\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n decisionId,\n });\n if (!retried.ok) return c.json({ error: retried.code }, 409);\n const { decision } = retried;\n return c.json({ decision: decisionSummary(boards, decision) });\n },\n }),\n\n // ── Create (upsert on sourceKey) a work item ─────────────────────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemCreate.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemCreate.method,\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 try {\n const created = await createFactoryWorkItem({\n workItems,\n boards: await this.#boards.load(resolved),\n transitionService,\n audit: auditInput => audit.emit({ context: loose(c), input: auditInput }),\n orgId: resolved.orgId,\n userId: resolved.userId,\n factoryProjectId: resolved.factoryProjectId,\n input,\n origin: auditRequestOrigin(loose(c)),\n });\n switch (created.status) {\n case 'refused':\n if (created.code === 'factory_transitions_unavailable') return c.json({ error: created.code }, 503);\n return c.json(\n { error: created.code, message: created.message },\n created.code === 'invalid_board' ? 422 : 409,\n );\n case 'rejected':\n return c.json({ status: 'rejected', code: created.code, reason: created.reason }, 422);\n case 'reused': {\n // Source-key reuse: the POST updated an existing card, not created one.\n const { stages: _stages, sessions: _sessions, ...boundedPatch } = input;\n await this.#auditWorkItemPatch({\n context: loose(c),\n item: created.item,\n patch: boundedPatch as unknown as Record<string, unknown>,\n });\n return c.json({ workItem: toWireWorkItem(created.item) });\n }\n case 'created':\n return c.json({ workItem: toWireWorkItem(created.item) });\n }\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(FACTORY_ROUTE_CONTRACTS.workItemTransition.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemTransition.method,\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 context = loose(c);\n const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemTransition.pathSchema.safeParse({\n id: resolved.factoryProjectId,\n workItemId: context.req.param('workItemId'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n const { workItemId } = parsedPath.data;\n const parsed = parseTransitionBody(await readJson(context));\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 ...auditRequestOrigin(loose(c)),\n ingress: {\n ...parsed.ingress,\n identity: `human:${resolved.userId}:${parsed.ingress.identity}`,\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(FACTORY_ROUTE_CONTRACTS.workItemStart.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemStart.method,\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(\n await readJson(loose(c)),\n resolved,\n resolved.factoryProjectId,\n resolved.modelScope,\n );\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 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 if (error instanceof WorkItemRepositoryError) {\n return c.json({ error: error.code, message: error.message, candidates: error.candidates }, 409);\n }\n throw error;\n }\n return c.json({ prepared }, 202);\n },\n }),\n\n // ── Patch non-stage metadata / sessions / title ──────────────────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemUpdate.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemUpdate.method,\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 context = loose(c);\n const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemUpdate.pathSchema.safeParse({\n id: context.req.param('id'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n const { id } = parsedPath.data;\n\n const body = await readJson(context);\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\n await workItems.ensureReady();\n const existing = await workItems.get({ orgId: tenant.orgId, id });\n if (!existing) return c.json({ error: 'Work item not found' }, 404);\n if (patch.board !== undefined) {\n const boards = await this.#boards.load({\n orgId: tenant.orgId,\n factoryProjectId: existing.factoryProjectId,\n });\n const board = boards.get(patch.board);\n const stage = patch.stages?.length === 1 ? patch.stages[0] : undefined;\n if (existing.board !== null) {\n return c.json({ error: 'board_already_assigned' }, 409);\n }\n if (!board) {\n return c.json({ error: 'board_not_installed' }, 400);\n }\n if (!stage || !Object.prototype.hasOwnProperty.call(board.phases, stage)) {\n return c.json({ error: 'invalid_board_migration_phase' }, 400);\n }\n } else 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 try {\n const updated = await workItems.update({\n orgId: tenant.orgId,\n id,\n userId: tenant.userId,\n patch,\n ...(patch.board !== undefined ? { expectedRevision: existing.revision, expectedBoard: null } : {}),\n });\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 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 if (error instanceof WorkItemUpdateConflictError) {\n return c.json({ error: error.reason === 'board' ? 'board_already_assigned' : error.code }, 409);\n }\n throw error;\n }\n },\n }),\n\n // ── Remove a work item ───────────────────────────────────────────────────\n registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemDelete.path, {\n method: FACTORY_ROUTE_CONTRACTS.workItemDelete.method,\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 context = loose(c);\n const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemDelete.pathSchema.safeParse({\n id: context.req.param('id'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n const { id } = parsedPath.data;\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-errors.d.ts","sourceRoot":"","sources":["../../src/rules/dispatch-errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,gCAAgC,EACjC,MAAM,uCAAuC,CAAC;AAE/C,UAAU,8BAA8B;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"dispatch-errors.d.ts","sourceRoot":"","sources":["../../src/rules/dispatch-errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,gCAAgC,EACjC,MAAM,uCAAuC,CAAC;AAE/C,UAAU,8BAA8B;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAgCD,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,0BAA0B;IAD3C,YACW,IAAI,EAAE,0BAA0B,EACzC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY,EAIvB;CACF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAErF;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,gCAAgC,GAAG,IAAI,GAC5C,8BAA8B,CAEhC"}
|
|
@@ -40,6 +40,10 @@ const FAILURE_METADATA = {
|
|
|
40
40
|
canRetry: false,
|
|
41
41
|
label: "Git is unavailable in the workspace"
|
|
42
42
|
},
|
|
43
|
+
repository_sandbox_unresponsive: {
|
|
44
|
+
canRetry: true,
|
|
45
|
+
label: "Workspace sandbox stopped responding"
|
|
46
|
+
},
|
|
43
47
|
repository_egress_blocked: {
|
|
44
48
|
canRetry: false,
|
|
45
49
|
label: "Repository network access is blocked"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type {\n FactoryDispatchFailureCode,\n StoredFactoryDispatchFailureCode,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n source_repository_ambiguous: { canRetry: true, label: 'Choose a repository for this work item' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n run_terminal_event_missing: { canRetry: false, label: 'Agent run terminal event was not observed' },\n skill_delivery_ambiguous: { canRetry: false, label: 'Factory skill delivery could not be confirmed' },\n run_overdue: { canRetry: false, label: 'Agent run is overdue' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n run_configuration_invalid: { canRetry: false, label: 'Run configuration rejected by provider' },\n run_credentials_unavailable: { canRetry: true, label: 'Model credentials unavailable for the run' },\n // The run's request carries no user, so every retry fails the same way; a person has to start it.\n run_user_unauthenticated: { canRetry: false, label: 'Run started without an authenticated user' },\n run_failed: { canRetry: true, label: 'Agent run ended in error' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n // Retired: no path writes these any more, stored rows still read through here.\n plan_awaiting_approval: { canRetry: false, label: 'Plan waiting for review' },\n run_awaiting_input: { canRetry: false, label: 'Agent is waiting for an answer' },\n} satisfies Record<StoredFactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: StoredFactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAUA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,6BAA6B;EAAE,UAAU;EAAM,OAAO;CAAyC;CAC/F,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,4BAA4B;EAAE,UAAU;EAAO,OAAO;CAA4C;CAClG,0BAA0B;EAAE,UAAU;EAAO,OAAO;CAAgD;CACpG,aAAa;EAAE,UAAU;EAAO,OAAO;CAAuB;CAC9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAyC;CAC9F,6BAA6B;EAAE,UAAU;EAAM,OAAO;CAA4C;CAElG,0BAA0B;EAAE,UAAU;EAAO,OAAO;CAA4C;CAChG,YAAY;EAAE,UAAU;EAAM,OAAO;CAA2B;CAChE,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;CAE9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA0B;CAC5E,oBAAoB;EAAE,UAAU;EAAO,OAAO;CAAiC;AACjF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
1
|
+
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type {\n FactoryDispatchFailureCode,\n StoredFactoryDispatchFailureCode,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n source_repository_ambiguous: { canRetry: true, label: 'Choose a repository for this work item' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n run_terminal_event_missing: { canRetry: false, label: 'Agent run terminal event was not observed' },\n skill_delivery_ambiguous: { canRetry: false, label: 'Factory skill delivery could not be confirmed' },\n run_overdue: { canRetry: false, label: 'Agent run is overdue' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_sandbox_unresponsive: { canRetry: true, label: 'Workspace sandbox stopped responding' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n run_configuration_invalid: { canRetry: false, label: 'Run configuration rejected by provider' },\n run_credentials_unavailable: { canRetry: true, label: 'Model credentials unavailable for the run' },\n // The run's request carries no user, so every retry fails the same way; a person has to start it.\n run_user_unauthenticated: { canRetry: false, label: 'Run started without an authenticated user' },\n run_failed: { canRetry: true, label: 'Agent run ended in error' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n // Retired: no path writes these any more, stored rows still read through here.\n plan_awaiting_approval: { canRetry: false, label: 'Plan waiting for review' },\n run_awaiting_input: { canRetry: false, label: 'Agent is waiting for an answer' },\n} satisfies Record<StoredFactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: StoredFactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAUA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,6BAA6B;EAAE,UAAU;EAAM,OAAO;CAAyC;CAC/F,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,4BAA4B;EAAE,UAAU;EAAO,OAAO;CAA4C;CAClG,0BAA0B;EAAE,UAAU;EAAO,OAAO;CAAgD;CACpG,aAAa;EAAE,UAAU;EAAO,OAAO;CAAuB;CAC9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,iCAAiC;EAAE,UAAU;EAAM,OAAO;CAAuC;CACjG,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAyC;CAC9F,6BAA6B;EAAE,UAAU;EAAM,OAAO;CAA4C;CAElG,0BAA0B;EAAE,UAAU;EAAO,OAAO;CAA4C;CAChG,YAAY;EAAE,UAAU;EAAM,OAAO;CAA2B;CAChE,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;CAE9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA0B;CAC5E,oBAAoB;EAAE,UAAU;EAAO,OAAO;CAAiC;AACjF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
2
2
|
import type { AgentController } from '@mastra/core/agent-controller';
|
|
3
3
|
import type { BoardRegistry } from '../boards/index.js';
|
|
4
|
+
import type { ProjectBoardSource } from '../boards/project-boards.js';
|
|
4
5
|
import type { AuditRecorder } from '../storage/domains/audit/domain.js';
|
|
5
6
|
import type { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';
|
|
6
7
|
import type { FactoryDeferredDecisionRecord, WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
@@ -19,7 +20,7 @@ export interface FactoryDecisionDispatcherOptions {
|
|
|
19
20
|
transitionService: Pick<FactoryTransitionService, 'transition' | 'configVersion'>;
|
|
20
21
|
storage: WorkItemsStorage;
|
|
21
22
|
/** Installed boards; defaults to the built-in Work and Review boards. */
|
|
22
|
-
boards?: BoardRegistry;
|
|
23
|
+
boards?: BoardRegistry | ProjectBoardSource;
|
|
23
24
|
ownerId?: string;
|
|
24
25
|
/** `false` parks `invokeSkill` effects as `proposed`; every other effect still runs. */
|
|
25
26
|
isAutoRunEnabled: (tenant: {
|
|
@@ -56,6 +57,11 @@ export interface FactoryDecisionDispatcherOptions {
|
|
|
56
57
|
type: 'upsertLinkedWorkItem';
|
|
57
58
|
}>;
|
|
58
59
|
}) => Promise<string | null>;
|
|
60
|
+
/** `false` spends a linked-card decision unfiled: its Factory project was deleted after the decision committed. */
|
|
61
|
+
factoryProjectExists?: (tenant: {
|
|
62
|
+
orgId: string;
|
|
63
|
+
factoryProjectId: string;
|
|
64
|
+
}) => Promise<boolean>;
|
|
59
65
|
maxInFlight?: number;
|
|
60
66
|
/** How often the stale-binding sweep runs. Defaults to 10 minutes. */
|
|
61
67
|
staleBindingSweepIntervalMs?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAyC,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"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"}
|
package/dist/rules/dispatcher.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FACTORY_RULE_STAGES, externalSourceForWorkItem, externallyAuthoredWorkItem } from "./types.js";
|
|
2
2
|
import { assertFactoryDecisionTarget, validateFactoryRuleDecision } from "./validation.js";
|
|
3
3
|
import { boardForWorkItem, resolvePhaseSemantics, workItemPhaseSemantics } from "../boards/semantics.js";
|
|
4
|
-
import { createBoardRegistry } from "../boards/registry.js";
|
|
5
4
|
import "../boards/index.js";
|
|
5
|
+
import { toBoardSource } from "../boards/project-boards.js";
|
|
6
6
|
import { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from "./dispatch-errors.js";
|
|
7
7
|
import { FACTORY_RULE_MATERIALIZATION_KEY, WorkItemClaimConflictError } from "../storage/domains/work-items/base.js";
|
|
8
8
|
import { moveCardToBoard } from "../boards/relocate.js";
|
|
@@ -299,6 +299,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
299
299
|
#feedReader;
|
|
300
300
|
#dismissStaleReviews;
|
|
301
301
|
#resolveLinkedWorkItemParentId;
|
|
302
|
+
#factoryProjectExists;
|
|
302
303
|
#maxInFlight;
|
|
303
304
|
#staleBindingSweepIntervalMs;
|
|
304
305
|
#staleBindingTtlMs;
|
|
@@ -320,7 +321,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
320
321
|
this.#audit = options.audit;
|
|
321
322
|
this.#controller = options.controller;
|
|
322
323
|
this.#transitionService = options.transitionService;
|
|
323
|
-
this.#boards = options.boards
|
|
324
|
+
this.#boards = toBoardSource(options.boards);
|
|
324
325
|
this.#storage = options.storage;
|
|
325
326
|
this.#ownerId = options.ownerId ?? `factory-dispatcher:${globalThis.crypto.randomUUID()}`;
|
|
326
327
|
this.#isAutoRunEnabled = options.isAutoRunEnabled;
|
|
@@ -332,6 +333,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
332
333
|
this.#feedReader = options.feedReader;
|
|
333
334
|
this.#dismissStaleReviews = options.dismissStaleReviews;
|
|
334
335
|
this.#resolveLinkedWorkItemParentId = options.resolveLinkedWorkItemParentId;
|
|
336
|
+
this.#factoryProjectExists = options.factoryProjectExists;
|
|
335
337
|
const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;
|
|
336
338
|
this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;
|
|
337
339
|
this.#staleBindingSweepIntervalMs = positiveMs(options.staleBindingSweepIntervalMs, STALE_BINDING_SWEEP_INTERVAL_MS);
|
|
@@ -510,7 +512,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
510
512
|
orgId: record.orgId,
|
|
511
513
|
id: record.workItemId
|
|
512
514
|
});
|
|
513
|
-
if (!item || workItemPhaseSemantics(this.#boards, item)?.kind !== "terminal") return;
|
|
515
|
+
if (!item || workItemPhaseSemantics(await this.#boards.load(record), item)?.kind !== "terminal") return;
|
|
514
516
|
await this.#storage.supersedeDecisionsForWorkItem({
|
|
515
517
|
orgId: record.orgId,
|
|
516
518
|
factoryProjectId: record.factoryProjectId,
|
|
@@ -543,7 +545,9 @@ var FactoryDecisionDispatcher = class {
|
|
|
543
545
|
}
|
|
544
546
|
}
|
|
545
547
|
async #needsApproval(record, decision) {
|
|
546
|
-
if (record.approvedAt !== null
|
|
548
|
+
if (record.approvedAt !== null) return false;
|
|
549
|
+
const boards = await this.#boards.load(record);
|
|
550
|
+
if (!requestsConsent(boards, record, decision)) return false;
|
|
547
551
|
const item = record.workItemId ? await this.#storage.get({
|
|
548
552
|
orgId: record.orgId,
|
|
549
553
|
id: record.workItemId
|
|
@@ -551,7 +555,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
551
555
|
if (item && externallyAuthoredWorkItem(item)) return true;
|
|
552
556
|
if (item?.autonomyArmedAt != null) return false;
|
|
553
557
|
if (item && reviewsFactoryAuthoredCode(item, decision)) return false;
|
|
554
|
-
if (item && startsInAutoPhase(
|
|
558
|
+
if (item && startsInAutoPhase(boards, item, decision)) return false;
|
|
555
559
|
return !await this.#isAutoRunEnabled({
|
|
556
560
|
orgId: record.orgId,
|
|
557
561
|
factoryProjectId: record.factoryProjectId
|
|
@@ -566,7 +570,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
566
570
|
switch (decision.type) {
|
|
567
571
|
case "transition": {
|
|
568
572
|
const item = await this.#requireItem(record);
|
|
569
|
-
if (!await this.#storage.getTransitionResultByIngress(record.orgId, record.factoryProjectId, `decision:${record.idempotencyKey}`)) assertFactoryDecisionTarget(decision, this.#boards, item.board ?? void 0);
|
|
573
|
+
if (!await this.#storage.getTransitionResultByIngress(record.orgId, record.factoryProjectId, `decision:${record.idempotencyKey}`)) assertFactoryDecisionTarget(decision, await this.#boards.load(record), item.board ?? void 0);
|
|
570
574
|
const result = await this.#transitionService.transition({
|
|
571
575
|
orgId: record.orgId,
|
|
572
576
|
factoryProjectId: record.factoryProjectId,
|
|
@@ -830,6 +834,10 @@ var FactoryDecisionDispatcher = class {
|
|
|
830
834
|
}
|
|
831
835
|
}
|
|
832
836
|
async #upsertLinkedItem(record, decision, causalChain) {
|
|
837
|
+
if (this.#factoryProjectExists && !await this.#factoryProjectExists({
|
|
838
|
+
orgId: record.orgId,
|
|
839
|
+
factoryProjectId: record.factoryProjectId
|
|
840
|
+
})) return;
|
|
833
841
|
const existing = await this.#storage.getByProjectSource({
|
|
834
842
|
orgId: record.orgId,
|
|
835
843
|
factoryProjectId: record.factoryProjectId,
|
|
@@ -842,8 +850,9 @@ var FactoryDecisionDispatcher = class {
|
|
|
842
850
|
if (replay?.status === "accepted" && replay.stage === decision.stage) return;
|
|
843
851
|
}
|
|
844
852
|
}
|
|
845
|
-
|
|
846
|
-
|
|
853
|
+
const boards = await this.#boards.load(record);
|
|
854
|
+
assertFactoryDecisionTarget(decision, boards);
|
|
855
|
+
const definition = boards.get(decision.board);
|
|
847
856
|
if (!definition) throw new Error("Factory decision target board is not installed.");
|
|
848
857
|
const initialPhase = definition.initialPhase;
|
|
849
858
|
const skipRules = decision.skipRules === true;
|
|
@@ -853,37 +862,43 @@ var FactoryDecisionDispatcher = class {
|
|
|
853
862
|
factoryProjectId: record.factoryProjectId,
|
|
854
863
|
decision
|
|
855
864
|
}) ?? null;
|
|
865
|
+
const upsert = () => this.#storage.upsert({
|
|
866
|
+
orgId: record.orgId,
|
|
867
|
+
userId: "factory-rule-dispatcher",
|
|
868
|
+
factoryProjectId: record.factoryProjectId,
|
|
869
|
+
input: {
|
|
870
|
+
externalSource: externalSourceForDecision(decision),
|
|
871
|
+
...decision.claimKey ? { claimKey: decision.claimKey } : {},
|
|
872
|
+
parentWorkItemId,
|
|
873
|
+
title: decision.title,
|
|
874
|
+
board: decision.board,
|
|
875
|
+
stages: [entryStage],
|
|
876
|
+
sessions: {},
|
|
877
|
+
metadata: {
|
|
878
|
+
...decision.metadata,
|
|
879
|
+
[FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
reuseMode: "preserve"
|
|
883
|
+
});
|
|
856
884
|
let result;
|
|
857
885
|
try {
|
|
858
|
-
result = await
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
factoryProjectId: record.factoryProjectId,
|
|
862
|
-
input: {
|
|
863
|
-
externalSource: externalSourceForDecision(decision),
|
|
864
|
-
...decision.claimKey ? { claimKey: decision.claimKey } : {},
|
|
865
|
-
parentWorkItemId,
|
|
866
|
-
title: decision.title,
|
|
867
|
-
board: decision.board,
|
|
868
|
-
stages: [entryStage],
|
|
869
|
-
sessions: {},
|
|
870
|
-
metadata: {
|
|
871
|
-
...decision.metadata,
|
|
872
|
-
[FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey
|
|
873
|
-
}
|
|
874
|
-
},
|
|
875
|
-
reuseMode: "preserve"
|
|
886
|
+
result = await upsert().catch(async (error) => {
|
|
887
|
+
if (error instanceof WorkItemClaimConflictError && await this.#releaseDeletedProjectClaim(record.orgId, decision.claimKey)) return upsert();
|
|
888
|
+
throw error;
|
|
876
889
|
});
|
|
877
890
|
} catch (error) {
|
|
878
891
|
if (error instanceof WorkItemClaimConflictError) return;
|
|
879
892
|
throw error;
|
|
880
893
|
}
|
|
881
|
-
|
|
882
|
-
|
|
894
|
+
const { claimKey } = decision;
|
|
895
|
+
if (!result.created && claimKey && !result.item.claimKey) {
|
|
896
|
+
const claim = {
|
|
883
897
|
orgId: record.orgId,
|
|
884
898
|
id: result.item.id,
|
|
885
|
-
claimKey
|
|
886
|
-
}
|
|
899
|
+
claimKey
|
|
900
|
+
};
|
|
901
|
+
const claimed = await this.#storage.claimWorkItem(claim) ?? (await this.#releaseDeletedProjectClaim(record.orgId, claimKey) ? await this.#storage.claimWorkItem(claim) : null);
|
|
887
902
|
if (claimed) result = {
|
|
888
903
|
...result,
|
|
889
904
|
item: claimed
|
|
@@ -921,7 +936,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
921
936
|
if (skipRules) {
|
|
922
937
|
if (!result.created) await moveCardToBoard({
|
|
923
938
|
workItems: this.#storage,
|
|
924
|
-
boardRegistry:
|
|
939
|
+
boardRegistry: boards,
|
|
925
940
|
userId: "factory-rule-dispatcher",
|
|
926
941
|
item: result.item,
|
|
927
942
|
targetBoard: decision.board,
|
|
@@ -981,6 +996,24 @@ var FactoryDecisionDispatcher = class {
|
|
|
981
996
|
});
|
|
982
997
|
if (moved.status === "rejected") throw new Error(`${moved.code}: ${moved.reason}`);
|
|
983
998
|
}
|
|
999
|
+
/** Drops `claimKey` when the card holding it belongs to a deleted Factory project; true when it is free again. */
|
|
1000
|
+
async #releaseDeletedProjectClaim(orgId, claimKey) {
|
|
1001
|
+
if (!claimKey || !this.#factoryProjectExists) return false;
|
|
1002
|
+
const holder = await this.#storage.getByClaimKey({
|
|
1003
|
+
orgId,
|
|
1004
|
+
claimKey
|
|
1005
|
+
});
|
|
1006
|
+
if (!holder) return true;
|
|
1007
|
+
if (await this.#factoryProjectExists({
|
|
1008
|
+
orgId,
|
|
1009
|
+
factoryProjectId: holder.factoryProjectId
|
|
1010
|
+
})) return false;
|
|
1011
|
+
await this.#storage.releaseProjectClaims({
|
|
1012
|
+
orgId,
|
|
1013
|
+
factoryProjectId: holder.factoryProjectId
|
|
1014
|
+
});
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
984
1017
|
async #requireItem(record) {
|
|
985
1018
|
if (!record.workItemId) throw new Error("Factory decision is not linked to a work item.");
|
|
986
1019
|
const item = await this.#storage.get({
|
|
@@ -1026,7 +1059,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
1026
1059
|
id: record.workItemId
|
|
1027
1060
|
}), this.#storage.listDecisionsForEvaluations(record.orgId, record.factoryProjectId, [record.evaluationId])]);
|
|
1028
1061
|
return decisionOvertaken({
|
|
1029
|
-
boards: this.#boards,
|
|
1062
|
+
boards: await this.#boards.load(record),
|
|
1030
1063
|
item,
|
|
1031
1064
|
record,
|
|
1032
1065
|
role,
|
|
@@ -1042,7 +1075,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
1042
1075
|
orgId: record.orgId,
|
|
1043
1076
|
id: record.workItemId
|
|
1044
1077
|
}).catch(() => null);
|
|
1045
|
-
if (item && workItemPhaseSemantics(this.#boards, item)?.kind === "terminal") return true;
|
|
1078
|
+
if (item && workItemPhaseSemantics(await this.#boards.load(record), item)?.kind === "terminal") return true;
|
|
1046
1079
|
return own.some((revoked) => revoked.revokedAt !== null && revoked.revokedAt.getTime() >= record.createdAt.getTime() && bindings.some((successor) => successor.role !== role && successor.status === "active" && successor.resourceId === revoked.resourceId && successor.sessionId === revoked.sessionId && successor.threadId === revoked.threadId && successor.createdAt.getTime() >= revoked.revokedAt.getTime()));
|
|
1047
1080
|
}
|
|
1048
1081
|
async #requireOrPrepareBinding(record, role) {
|
|
@@ -1075,7 +1108,10 @@ var FactoryDecisionDispatcher = class {
|
|
|
1075
1108
|
orgId,
|
|
1076
1109
|
id: workItemId
|
|
1077
1110
|
}) : null;
|
|
1078
|
-
if (item && autoApprovedExitTool(this.#boards
|
|
1111
|
+
if (item && autoApprovedExitTool(await this.#boards.load({
|
|
1112
|
+
orgId,
|
|
1113
|
+
factoryProjectId
|
|
1114
|
+
}), item) === "submit_plan") return true;
|
|
1079
1115
|
return this.#autoApprovePlans ? await this.#autoApprovePlans({
|
|
1080
1116
|
orgId,
|
|
1081
1117
|
factoryProjectId
|