@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.
Files changed (180) hide show
  1. package/README.md +74 -7
  2. package/dist/boards/define-board.d.ts +19 -0
  3. package/dist/boards/define-board.d.ts.map +1 -1
  4. package/dist/boards/define-board.js +41 -9
  5. package/dist/boards/define-board.js.map +1 -1
  6. package/dist/boards/project-boards.d.ts +78 -0
  7. package/dist/boards/project-boards.d.ts.map +1 -0
  8. package/dist/boards/project-boards.js +194 -0
  9. package/dist/boards/project-boards.js.map +1 -0
  10. package/dist/boards/review.js +2 -2
  11. package/dist/boards/review.js.map +1 -1
  12. package/dist/boards/stored-board.d.ts +101 -0
  13. package/dist/boards/stored-board.d.ts.map +1 -0
  14. package/dist/boards/stored-board.js +139 -0
  15. package/dist/boards/stored-board.js.map +1 -0
  16. package/dist/boards/work.d.ts.map +1 -1
  17. package/dist/boards/work.js +6 -3
  18. package/dist/boards/work.js.map +1 -1
  19. package/dist/capabilities/intake.d.ts +6 -0
  20. package/dist/capabilities/intake.d.ts.map +1 -1
  21. package/dist/environment/build.d.ts +1 -1
  22. package/dist/environment/build.d.ts.map +1 -1
  23. package/dist/environment/build.js +2 -0
  24. package/dist/environment/build.js.map +1 -1
  25. package/dist/factory.d.ts +15 -2
  26. package/dist/factory.d.ts.map +1 -1
  27. package/dist/factory.js +51 -12
  28. package/dist/factory.js.map +1 -1
  29. package/dist/integrations/github/integration.d.ts +5 -4
  30. package/dist/integrations/github/integration.d.ts.map +1 -1
  31. package/dist/integrations/github/integration.js +10 -8
  32. package/dist/integrations/github/integration.js.map +1 -1
  33. package/dist/integrations/github/sandbox.d.ts +2 -2
  34. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  35. package/dist/integrations/github/sandbox.js +6 -1
  36. package/dist/integrations/github/sandbox.js.map +1 -1
  37. package/dist/integrations/jira/binding-scope.d.ts +30 -0
  38. package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
  39. package/dist/integrations/jira/binding-scope.js +50 -0
  40. package/dist/integrations/jira/binding-scope.js.map +1 -0
  41. package/dist/integrations/jira/claim.d.ts +12 -0
  42. package/dist/integrations/jira/claim.d.ts.map +1 -1
  43. package/dist/integrations/jira/claim.js +24 -1
  44. package/dist/integrations/jira/claim.js.map +1 -1
  45. package/dist/integrations/jira/intake-jql.d.ts +32 -0
  46. package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
  47. package/dist/integrations/jira/intake-jql.js +57 -0
  48. package/dist/integrations/jira/intake-jql.js.map +1 -0
  49. package/dist/integrations/jira/integration.d.ts +4 -3
  50. package/dist/integrations/jira/integration.d.ts.map +1 -1
  51. package/dist/integrations/jira/integration.js +14 -21
  52. package/dist/integrations/jira/integration.js.map +1 -1
  53. package/dist/integrations/jira/routes.d.ts +3 -0
  54. package/dist/integrations/jira/routes.d.ts.map +1 -1
  55. package/dist/integrations/jira/routes.js +16 -29
  56. package/dist/integrations/jira/routes.js.map +1 -1
  57. package/dist/integrations/jira/rules.d.ts.map +1 -1
  58. package/dist/integrations/jira/rules.js +8 -13
  59. package/dist/integrations/jira/rules.js.map +1 -1
  60. package/dist/integrations/platform/github/integration.d.ts +5 -0
  61. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  62. package/dist/integrations/platform/github/integration.js +7 -0
  63. package/dist/integrations/platform/github/integration.js.map +1 -1
  64. package/dist/integrations/platform/jira/integration.d.ts +7 -1
  65. package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
  66. package/dist/integrations/platform/jira/integration.js +26 -24
  67. package/dist/integrations/platform/jira/integration.js.map +1 -1
  68. package/dist/integrations/platform/jira/routes.d.ts +3 -0
  69. package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
  70. package/dist/integrations/platform/jira/routes.js +17 -28
  71. package/dist/integrations/platform/jira/routes.js.map +1 -1
  72. package/dist/routes/attention-providers.d.ts +2 -1
  73. package/dist/routes/attention-providers.d.ts.map +1 -1
  74. package/dist/routes/attention-providers.js +3 -2
  75. package/dist/routes/attention-providers.js.map +1 -1
  76. package/dist/routes/attention.d.ts +2 -1
  77. package/dist/routes/attention.d.ts.map +1 -1
  78. package/dist/routes/attention.js.map +1 -1
  79. package/dist/routes/boards.d.ts +30 -0
  80. package/dist/routes/boards.d.ts.map +1 -0
  81. package/dist/routes/boards.js +272 -0
  82. package/dist/routes/boards.js.map +1 -0
  83. package/dist/routes/contracts.d.ts +249 -0
  84. package/dist/routes/contracts.d.ts.map +1 -1
  85. package/dist/routes/contracts.js +107 -35
  86. package/dist/routes/contracts.js.map +1 -1
  87. package/dist/routes/intake.d.ts +15 -10
  88. package/dist/routes/intake.d.ts.map +1 -1
  89. package/dist/routes/intake.js +109 -22
  90. package/dist/routes/intake.js.map +1 -1
  91. package/dist/routes/projects.d.ts +3 -0
  92. package/dist/routes/projects.d.ts.map +1 -1
  93. package/dist/routes/projects.js +10 -1
  94. package/dist/routes/projects.js.map +1 -1
  95. package/dist/routes/surface.d.ts +10 -3
  96. package/dist/routes/surface.d.ts.map +1 -1
  97. package/dist/routes/surface.js +13 -1
  98. package/dist/routes/surface.js.map +1 -1
  99. package/dist/routes/tenant-credentials.d.ts +9 -0
  100. package/dist/routes/tenant-credentials.d.ts.map +1 -1
  101. package/dist/routes/tenant-credentials.js +24 -2
  102. package/dist/routes/tenant-credentials.js.map +1 -1
  103. package/dist/routes/work-items.d.ts +3 -1
  104. package/dist/routes/work-items.d.ts.map +1 -1
  105. package/dist/routes/work-items.js +64 -42
  106. package/dist/routes/work-items.js.map +1 -1
  107. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  108. package/dist/rules/dispatch-errors.js +4 -0
  109. package/dist/rules/dispatch-errors.js.map +1 -1
  110. package/dist/rules/dispatcher.d.ts +7 -1
  111. package/dist/rules/dispatcher.d.ts.map +1 -1
  112. package/dist/rules/dispatcher.js +70 -34
  113. package/dist/rules/dispatcher.js.map +1 -1
  114. package/dist/rules/processor.d.ts +3 -1
  115. package/dist/rules/processor.d.ts.map +1 -1
  116. package/dist/rules/processor.js +11 -6
  117. package/dist/rules/processor.js.map +1 -1
  118. package/dist/rules/tools.d.ts +2 -1
  119. package/dist/rules/tools.d.ts.map +1 -1
  120. package/dist/rules/tools.js +2 -1
  121. package/dist/rules/tools.js.map +1 -1
  122. package/dist/rules/transition-service.d.ts +3 -1
  123. package/dist/rules/transition-service.d.ts.map +1 -1
  124. package/dist/rules/transition-service.js +12 -12
  125. package/dist/rules/transition-service.js.map +1 -1
  126. package/dist/sandbox/local-factory-sandbox.d.ts +4 -1
  127. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
  128. package/dist/sandbox/local-factory-sandbox.js +1 -1
  129. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  130. package/dist/storage/domains/audit/actions.d.ts +1 -0
  131. package/dist/storage/domains/audit/actions.d.ts.map +1 -1
  132. package/dist/storage/domains/audit/actions.js +6 -1
  133. package/dist/storage/domains/audit/actions.js.map +1 -1
  134. package/dist/storage/domains/boards/base.d.ts +110 -0
  135. package/dist/storage/domains/boards/base.d.ts.map +1 -0
  136. package/dist/storage/domains/boards/base.js +292 -0
  137. package/dist/storage/domains/boards/base.js.map +1 -0
  138. package/dist/storage/domains/boards/schemas.d.ts +11 -0
  139. package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
  140. package/dist/storage/domains/boards/schemas.js +106 -0
  141. package/dist/storage/domains/boards/schemas.js.map +1 -0
  142. package/dist/storage/domains/credentials/base.d.ts +4 -1
  143. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  144. package/dist/storage/domains/credentials/base.js +5 -2
  145. package/dist/storage/domains/credentials/base.js.map +1 -1
  146. package/dist/storage/domains/intake/base.d.ts +86 -5
  147. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  148. package/dist/storage/domains/intake/base.js +298 -59
  149. package/dist/storage/domains/intake/base.js.map +1 -1
  150. package/dist/storage/domains/projects/base.d.ts +2 -1
  151. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  152. package/dist/storage/domains/projects/base.js +15 -10
  153. package/dist/storage/domains/projects/base.js.map +1 -1
  154. package/dist/storage/domains/work-items/base.d.ts +16 -2
  155. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  156. package/dist/storage/domains/work-items/base.js +40 -11
  157. package/dist/storage/domains/work-items/base.js.map +1 -1
  158. package/dist/tools/read-tools.d.ts +3 -1
  159. package/dist/tools/read-tools.d.ts.map +1 -1
  160. package/dist/tools/read-tools.js +3 -2
  161. package/dist/tools/read-tools.js.map +1 -1
  162. package/dist/tools/session-tools.d.ts +3 -1
  163. package/dist/tools/session-tools.d.ts.map +1 -1
  164. package/dist/tools/session-tools.js +2 -1
  165. package/dist/tools/session-tools.js.map +1 -1
  166. package/dist/tools/sessions-state-processor.d.ts +3 -1
  167. package/dist/tools/sessions-state-processor.d.ts.map +1 -1
  168. package/dist/tools/sessions-state-processor.js +2 -1
  169. package/dist/tools/sessions-state-processor.js.map +1 -1
  170. package/dist/tools/work-item-tools.d.ts +3 -1
  171. package/dist/tools/work-item-tools.d.ts.map +1 -1
  172. package/dist/tools/work-item-tools.js +3 -2
  173. package/dist/tools/work-item-tools.js.map +1 -1
  174. package/dist/workspace.d.ts +2 -0
  175. package/dist/workspace.d.ts.map +1 -1
  176. package/dist/workspace.js +14 -5
  177. package/dist/workspace.js.map +1 -1
  178. package/factory-skills/factory-plan/SKILL.md +1 -1
  179. package/factory-skills/factory-triage/SKILL.md +18 -7
  180. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/rules/processor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/rules/processor.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { workItemSource } from "./types.js";
2
2
  import { assertFactoryDecisionTarget, normalizeFactoryRuleJsonValue, validateFactoryRuleDecisions } from "./validation.js";
3
3
  import { boardForWorkItem, resolveBoardToolRule, workItemPhaseSemantics } from "../boards/semantics.js";
4
4
  import "../boards/index.js";
5
+ import { toBoardSource } from "../boards/project-boards.js";
5
6
  import { findSessionRunBinding, getFactorySessionCoordinates } from "./binding-context.js";
6
7
  import { resolveRequestThinkingLevel } from "@mastra/code-sdk/agents/model";
7
8
  // src/rules/processor.ts
@@ -158,8 +159,10 @@ var FactoryPhaseStateProcessor = class {
158
159
  stateId = STATE_ID;
159
160
  /** Request contexts whose caller row is already resolved for this request. */
160
161
  loadedMemorySettings = /* @__PURE__ */ new WeakSet();
162
+ boards;
161
163
  constructor(options) {
162
164
  this.options = options;
165
+ this.boards = toBoardSource(options.boards);
163
166
  }
164
167
  /**
165
168
  * Memory settings are caller state, not run state. Factory invokes this while
@@ -247,7 +250,8 @@ var FactoryPhaseStateProcessor = class {
247
250
  orgId: binding.orgId,
248
251
  id: binding.workItemId
249
252
  });
250
- const stage = ruleStage(this.options.boards, item);
253
+ const boards = await this.boards.load(binding);
254
+ const stage = ruleStage(boards, item);
251
255
  if (!item || !stage) return;
252
256
  const linked = (await this.options.storage.list({
253
257
  orgId: binding.orgId,
@@ -274,7 +278,7 @@ var FactoryPhaseStateProcessor = class {
274
278
  const cacheKey = await phaseCacheKey(value, linked);
275
279
  if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey) return;
276
280
  const linkedText = linked.length ? `\nLinked items: ${linked.map((candidate) => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join("; ")}` : "";
277
- const phase = this.options.boards.get(board)?.phases[stage];
281
+ const phase = boards.get(board)?.phases[stage];
278
282
  const phaseLabel = phase?.name ?? stage;
279
283
  const exitText = phase?.kind === "working" && phase.exitTool ? `This phase ends when ${escapeText(phase.exitTool.id)} succeeds. Then move the card with factory_transition_work_item to one of: ${phase.nextPhases.map(escapeText).join(", ")}.\n` : "";
280
284
  const runtime = value.modelId && value.thinkingLevel ? {
@@ -314,7 +318,7 @@ var FactoryPhaseStateProcessor = class {
314
318
  orgId: binding.orgId,
315
319
  id: binding.workItemId
316
320
  });
317
- if (!itemInRuleStage(this.options.boards, item)) return;
321
+ if (!itemInRuleStage(await this.boards.load(binding), item)) return;
318
322
  const cursor = await this.options.storage.getToolResultCursor(binding.orgId, binding.factoryProjectId, binding.id);
319
323
  let page = 0;
320
324
  while (true) {
@@ -348,7 +352,7 @@ var FactoryPhaseStateProcessor = class {
348
352
  orgId: binding.orgId,
349
353
  id: binding.workItemId
350
354
  });
351
- if (!itemInRuleStage(this.options.boards, item)) return;
355
+ if (!itemInRuleStage(await this.boards.load(binding), item)) return;
352
356
  for (const message of messages) for (const toolResult of completedToolResults(message)) {
353
357
  if (toolCallIds && !toolCallIds.has(toolResult.toolCallId)) continue;
354
358
  try {
@@ -368,7 +372,8 @@ var FactoryPhaseStateProcessor = class {
368
372
  }
369
373
  async ingestToolResult(binding, item, toolResult) {
370
374
  const board = boardForWorkItem(item);
371
- const rule = resolveBoardToolRule(this.options.boards, board, toolResult.toolName);
375
+ const boards = await this.boards.load(binding);
376
+ const rule = resolveBoardToolRule(boards, board, toolResult.toolName);
372
377
  if (!rule) return;
373
378
  const ingressId = JSON.stringify([
374
379
  binding.id,
@@ -428,7 +433,7 @@ var FactoryPhaseStateProcessor = class {
428
433
  };
429
434
  else if (decision) {
430
435
  const validated = validateFactoryRuleDecisions([decision]);
431
- for (const entry of validated) assertFactoryDecisionTarget(entry, this.options.boards, board);
436
+ for (const entry of validated) assertFactoryDecisionTarget(entry, boards, board);
432
437
  decisions = validated;
433
438
  }
434
439
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"processor.js","names":[],"sources":["../../src/rules/processor.ts"],"sourcesContent":["import { resolveRequestThinkingLevel } from '@mastra/code-sdk/agents/model';\nimport type { ThinkingLevel } from '@mastra/code-sdk/providers/openai-codex';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { MastraDBMessage, MessageList } from '@mastra/core/agent/message-list';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type {\n ComputeStateSignalArgs,\n ComputeStateSignalResult,\n ProcessInputArgs,\n ProcessInputStepArgs,\n Processor,\n} from '@mastra/core/processors';\n\nimport { boardForWorkItem, resolveBoardToolRule, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryRunBindingRecord, WorkItemsStorage, WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { findSessionRunBinding, getFactorySessionCoordinates } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { workItemSource } from './types.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRuleStage,\n FactoryToolResultRuleContext,\n} from './types.js';\nimport {\n assertFactoryDecisionTarget,\n normalizeFactoryRuleJsonValue,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst STATE_ID = 'factory-phase';\nconst RULE_TIMEOUT_MS = 5_000;\nconst TRANSCRIPT_PAGE_SIZE = 50;\nconst MAX_LINKED_ITEMS = 5;\nfunction ruleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): string | undefined {\n return item && workItemPhaseSemantics(boards, item) ? item.stages[0] : undefined;\n}\n\nfunction itemInRuleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): item is WorkItemRow {\n return ruleStage(boards, item) !== undefined;\n}\n\ntype PersistedMessageReader = {\n listMessages(input: {\n threadId: string;\n resourceId?: string;\n page: number;\n perPage: number;\n filter?: { dateRange?: { start?: Date } };\n orderBy: { field: 'createdAt'; direction: 'ASC' };\n }): Promise<{ messages: MastraDBMessage[]; hasMore: boolean }>;\n};\n\ntype CompletedToolResult = {\n assistantMessageId: string;\n messageCreatedAt: Date;\n toolCallId: string;\n toolName: string;\n input: FactoryRuleJsonValue;\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n};\n\nfunction isApprovedPlanResult(result: CompletedToolResult): boolean {\n if (result.toolName !== 'submit_plan' || result.status !== 'success') return false;\n if (typeof result.value === 'string') return result.value.startsWith('Plan approved.');\n if (!result.value || typeof result.value !== 'object' || Array.isArray(result.value)) return false;\n const content = result.value.content;\n return typeof content === 'string' && content.startsWith('Plan approved.');\n}\n\ntype RuntimeSnapshot = {\n modelId: string;\n thinkingLevel: ThinkingLevel;\n};\n\ntype ActivePhaseSnapshotBase = {\n bindingId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n role: string;\n configVersion: string;\n status: 'active';\n};\n\ntype ActivePhaseSnapshotValue = ActivePhaseSnapshotBase & {\n board: string;\n modelId?: string;\n thinkingLevel?: ThinkingLevel;\n};\n\ntype PhaseSnapshotValue = ActivePhaseSnapshotValue | { bindingId?: string; status: 'none' };\n\nfunction reviewRuntimeFromRequestContext(\n requestContext: ComputeStateSignalArgs['requestContext'],\n defaultThinkingLevel: ThinkingLevel | undefined,\n): RuntimeSnapshot {\n if (!requestContext || typeof requestContext.get !== 'function') {\n throw new Error('Factory review phase requires a controller request context.');\n }\n const context = requestContext.get<'controller', AgentControllerRequestContext<MastraCodeState>>('controller');\n const modelId = context?.session?.modelId.trim();\n if (!modelId) {\n throw new Error('Factory review phase requires a selected session model.');\n }\n return {\n modelId,\n thinkingLevel: resolveRequestThinkingLevel({ state: context?.state }, undefined, defaultThinkingLevel),\n };\n}\n\nfunction workItemSourceKey(item: WorkItemRow): string | null {\n const source = item.externalSource;\n return source ? `${source.integrationId}:${source.type}:${source.externalId}` : null;\n}\n\nfunction boundedError(value: unknown): FactoryRuleJsonValue {\n const message = value instanceof Error ? value.message : typeof value === 'string' ? value : 'Tool execution failed.';\n return { message: message.slice(0, 2_000) };\n}\n\nfunction boundedResult(value: unknown): FactoryRuleJsonValue {\n try {\n return normalizeFactoryRuleJsonValue(value);\n } catch {\n return { message: 'Tool result was not serializable.' };\n }\n}\n\nfunction messageParts(message: MastraDBMessage): unknown[] {\n const content = message.content as { parts?: unknown[]; toolInvocations?: unknown[] } | unknown[] | undefined;\n if (Array.isArray(content)) return content;\n if (Array.isArray(content?.parts)) return content.parts;\n return Array.isArray(content?.toolInvocations) ? content.toolInvocations : [];\n}\n\nfunction completedStepToolCallIds(steps: unknown[]): Set<string> {\n const ids = new Set<string>();\n for (const rawStep of steps) {\n if (!rawStep || typeof rawStep !== 'object') continue;\n const toolResults = (rawStep as { toolResults?: unknown[] }).toolResults;\n if (!Array.isArray(toolResults)) continue;\n for (const rawResult of toolResults) {\n if (!rawResult || typeof rawResult !== 'object') continue;\n const toolCallId = (rawResult as { toolCallId?: unknown }).toolCallId;\n if (typeof toolCallId === 'string') ids.add(toolCallId);\n }\n }\n return ids;\n}\n\nfunction completedToolResults(message: MastraDBMessage): CompletedToolResult[] {\n if (message.role !== 'assistant') return [];\n const createdAt = message.createdAt instanceof Date ? message.createdAt : new Date(message.createdAt);\n const completed: CompletedToolResult[] = [];\n for (const rawPart of messageParts(message)) {\n if (!rawPart || typeof rawPart !== 'object') continue;\n const part = rawPart as Record<string, unknown>;\n const invocation =\n part.type === 'tool-invocation' && part.toolInvocation && typeof part.toolInvocation === 'object'\n ? (part.toolInvocation as Record<string, unknown>)\n : part;\n const state = invocation.state;\n if (state !== 'result' && state !== 'error') continue;\n const toolCallId = invocation.toolCallId;\n const toolName = invocation.toolName ?? invocation.name;\n if (typeof toolCallId !== 'string' || typeof toolName !== 'string') continue;\n completed.push({\n assistantMessageId: message.id,\n messageCreatedAt: createdAt,\n toolCallId,\n toolName: toolName.slice(0, 256),\n input: boundedResult(invocation.args ?? {}),\n status: state === 'error' ? 'error' : 'success',\n value: state === 'error' ? boundedError(invocation.result ?? invocation.error) : boundedResult(invocation.result),\n });\n }\n return completed;\n}\n\nfunction currentCompletedToolMessage(\n messages: MastraDBMessage[],\n toolCallIds: ReadonlySet<string>,\n): MastraDBMessage | undefined {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]!;\n if (completedToolResults(message).some(result => toolCallIds.has(result.toolCallId))) return message;\n }\n return undefined;\n}\n\nasync function phaseCacheKey(value: ActivePhaseSnapshotValue, linked: WorkItemRow[]): Promise<string> {\n const serialized = JSON.stringify({\n ...value,\n linked: linked.map(item => [item.id, item.revision, item.stages[0]]),\n });\n return Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(serialized))).toString(\n 'hex',\n );\n}\n\nfunction escapeText(value: string): string {\n return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');\n}\n\nfunction phaseFromSignal(signal: { metadata?: Record<string, unknown> } | undefined): PhaseSnapshotValue | undefined {\n return (signal?.metadata?.value as { phase?: PhaseSnapshotValue } | undefined)?.phase;\n}\n\nfunction latestPhase(args: ComputeStateSignalArgs): PhaseSnapshotValue | undefined {\n for (const signal of [...args.activeStateSignals].reverse()) {\n const phase = phaseFromSignal(signal);\n if (phase) return phase;\n }\n return phaseFromSignal(args.lastSnapshot);\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nconst reportedUnavailableMemorySettings = new WeakSet<object>();\n\n/**\n * Marks this run's Factory memory settings as unavailable so memory falls back\n * to auto, and tells the thread once per request that saved choices were not\n * applied. The reserved request-context key keeps clients from spoofing it.\n */\nexport function reportMemorySettingsUnavailable(\n requestContext: NonNullable<ProcessInputArgs['requestContext']>,\n reason: string,\n): void {\n requestContext.set('mastra__factoryMemorySettings', { status: 'unavailable', reason });\n console.warn('[Factory Memory Settings] Failed to load settings for run', { error: reason });\n if (reportedUnavailableMemorySettings.has(requestContext)) return;\n reportedUnavailableMemorySettings.add(requestContext);\n const controller = requestContext.get('controller') as\n | Pick<AgentControllerRequestContext<MastraCodeState>, 'emitEvent'>\n | undefined;\n controller?.emitEvent?.({\n type: 'error',\n error: new Error(\n 'Memory settings could not be loaded for this run, so observational memory is using Auto models and default thresholds.',\n ),\n errorType: 'factory_memory_settings_unavailable',\n retryable: false,\n });\n}\n\nexport class FactoryPhaseStateProcessor implements Processor<'factory-phase'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n /** Request contexts whose caller row is already resolved for this request. */\n private readonly loadedMemorySettings = new WeakSet<object>();\n\n constructor(\n private readonly options: {\n configVersion: string;\n storage: WorkItemsStorage;\n boards: BoardRegistry;\n loadMemorySettings?: (args: {\n requestContext: ProcessInputStepArgs['requestContext'];\n binding: FactoryRunBindingRecord | null;\n }) => Promise<void>;\n /** The session's routed thinking default, used when the session has not picked a level. */\n defaultThinkingLevel?: (requestContext: ComputeStateSignalArgs['requestContext']) => Promise<ThinkingLevel>;\n transitionService?: Pick<FactoryTransitionService, 'transition'>;\n messageReader?: PersistedMessageReader;\n recordPullRequestProvenance?: (input: {\n binding: FactoryRunBindingRecord;\n item: WorkItemRow;\n assistantMessageId: string;\n toolCallId: string;\n toolName: string;\n toolInput: FactoryRuleJsonValue;\n toolResult: FactoryRuleJsonValue;\n status: 'success' | 'error';\n }) => Promise<void>;\n },\n ) {}\n\n /**\n * Memory settings are caller state, not run state. Factory invokes this while\n * resolving its configured processor list, before Agent resolves dynamic\n * Memory and enumerates Memory's processors. This guarantees both the Memory\n * options and its model callbacks see the authoritative row for this\n * invocation. Doing this in `processInputStep` would be too late: Memory's\n * input-step hook runs before configured processors.\n */\n async prepareMemorySettings(requestContext: ProcessInputArgs['requestContext']): Promise<void> {\n await this.loadCallerMemorySettings(requestContext, undefined, true);\n }\n\n async processInput(args: ProcessInputArgs): Promise<MessageList> {\n await this.loadCallerMemorySettings(args.requestContext);\n return args.messageList;\n }\n\n async processInputStep(args: ProcessInputStepArgs): Promise<MessageList | undefined> {\n const address = getFactorySessionCoordinates(args.requestContext);\n let binding: FactoryRunBindingRecord | null | undefined;\n try {\n binding = address ? await this.options.storage.findRunBindingBySession(address) : undefined;\n } catch {\n await this.loadCallerMemorySettings(args.requestContext, undefined);\n return;\n }\n // Resumed runs skip the input-processor phase, so re-check here: it is a\n // no-op once `processInput` already placed the row for this request. Passing\n // `undefined` (not `null`) keeps the recovery lookup inside the loader, so a\n // recovered session reaches its project row from this hook too.\n await this.loadCallerMemorySettings(args.requestContext, binding);\n if (!binding || binding.status !== 'active') return;\n const completedToolCallIds = completedStepToolCallIds(args.steps);\n const completedMessage = currentCompletedToolMessage(args.messages, completedToolCallIds);\n if (completedMessage) {\n await this.ingestMessages(binding, [completedMessage], completedToolCallIds);\n }\n }\n\n /**\n * Loads the caller's row once per invocation. The input hook always refreshes\n * it in case the controller reused a request context; later step hooks reuse\n * that value (`undefined` means it has not been loaded yet; `null` means an\n * empty row).\n *\n * The WeakSet covers loaders that do not set the context key themselves, so a\n * request is still loaded once. A failed load records an unavailable sentinel\n * (memory then runs both roles on auto with default thresholds) and is not\n * marked loaded so the next step can retry.\n */\n private async loadCallerMemorySettings(\n requestContext: ProcessInputStepArgs['requestContext'] | ProcessInputArgs['requestContext'],\n binding?: FactoryRunBindingRecord | null,\n force = false,\n ): Promise<void> {\n if (!requestContext || !this.options.loadMemorySettings) return;\n const currentSettings = requestContext.get('mastra__factoryMemorySettings') as\n | { status?: unknown }\n | null\n | undefined;\n if (force) this.loadedMemorySettings.delete(requestContext);\n if (!force && currentSettings !== undefined && currentSettings?.status !== 'unavailable') return;\n if (!force && this.loadedMemorySettings.has(requestContext)) return;\n try {\n let runBinding = binding ?? null;\n if (binding === undefined) {\n const address = getFactorySessionCoordinates(requestContext);\n runBinding = address\n ? await this.options.storage.findRunBindingBySession(address)\n : // Crash-recovered sessions come back with empty state, so the binding\n // table is the last source of the project whose row applies. Without\n // this the run would silently read the caller's personal row.\n await findSessionRunBinding(requestContext, this.options.storage);\n }\n await this.options.loadMemorySettings({ requestContext, binding: runBinding });\n this.loadedMemorySettings.add(requestContext);\n } catch (error) {\n reportMemorySettingsUnavailable(requestContext, error instanceof Error ? error.message : String(error));\n }\n }\n\n async computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult> {\n const address = getFactorySessionCoordinates(args.requestContext);\n if (!address) return;\n const binding = await this.options.storage.findRunBindingBySession(address);\n const prior = latestPhase(args);\n const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;\n\n if (!binding) return;\n if (binding.status !== 'active') {\n if (!hasBase || prior?.status !== 'active') return;\n return {\n id: STATE_ID,\n cacheKey: `factory:none:${prior.bindingId ?? 'revoked'}`,\n mode: 'snapshot',\n tagName: 'factory-phase',\n contents: '\\n',\n value: { phase: { status: 'none' } },\n attributes: { status: 'none' },\n metadata: { value: { phase: { status: 'none' } } },\n };\n }\n\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const stage = ruleStage(this.options.boards, item);\n if (!item || !stage) return;\n const allItems = await this.options.storage.list({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n });\n const linked = allItems\n .filter(candidate => candidate.parentWorkItemId === item.id || item.parentWorkItemId === candidate.id)\n .slice(0, MAX_LINKED_ITEMS);\n const board = boardForWorkItem(item);\n const baseValue: ActivePhaseSnapshotBase = {\n status: 'active',\n bindingId: binding.id,\n itemId: item.id,\n revision: item.revision,\n stage,\n role: binding.role,\n configVersion: this.options.configVersion,\n };\n const value: ActivePhaseSnapshotValue =\n board === 'review'\n ? {\n ...baseValue,\n board,\n ...reviewRuntimeFromRequestContext(\n args.requestContext,\n await this.options.defaultThinkingLevel?.(args.requestContext),\n ),\n }\n : { ...baseValue, board };\n const cacheKey = await phaseCacheKey(value, linked);\n if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey)\n return;\n\n const linkedText = linked.length\n ? `\\nLinked items: ${linked.map(candidate => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join('; ')}`\n : '';\n const phase = this.options.boards.get(board)?.phases[stage];\n const phaseLabel = phase?.name ?? stage;\n const exitText =\n phase?.kind === 'working' && phase.exitTool\n ? `This phase ends when ${escapeText(phase.exitTool.id)} succeeds. Then move the card with factory_transition_work_item to one of: ${phase.nextPhases.map(escapeText).join(', ')}.\\n`\n : '';\n const runtime =\n value.modelId && value.thinkingLevel ? { modelId: value.modelId, thinkingLevel: value.thinkingLevel } : null;\n const snapshotContents =\n `Factory ${board} phase: ${escapeText(phaseLabel)} (${escapeText(stage)})\\n` +\n `Work item: ${escapeText(item.title)} (${item.id})\\n` +\n `Role: ${escapeText(binding.role)}\\nRevision: ${item.revision}\\nConfig: ${escapeText(this.options.configVersion)}\\n` +\n (runtime\n ? `Runtime: model=${escapeText(runtime.modelId)}, reasoning-setting=${escapeText(runtime.thinkingLevel)}\\n`\n : '') +\n ((board === 'work' || board === 'review') && stage === 'intake'\n ? 'This card rests in Intake: its work is paused. Answer questions without moving it; when the user asks to resume, request the transition into the working stage first, then continue the work in this session.\\n'\n : '') +\n exitText +\n `Use factory_transition_work_item with expectedRevision ${item.revision} to request a phase change.${escapeText(linkedText)}`;\n const isDelta = hasBase && prior?.status === 'active';\n return {\n id: STATE_ID,\n cacheKey,\n mode: isDelta ? 'delta' : 'snapshot',\n tagName: 'factory-phase',\n contents: isDelta ? `Factory phase update:\\n${snapshotContents}` : snapshotContents,\n value: { phase: value },\n ...(isDelta ? { delta: { phase: value } } : {}),\n attributes: {\n status: 'active',\n board,\n stage,\n role: binding.role,\n revision: item.revision,\n ...(runtime ?? {}),\n },\n metadata: { value: { phase: value } },\n };\n }\n\n async reconcileAllBoundThreads(): Promise<void> {\n if (!this.options.messageReader) return;\n const bindings = await this.options.storage.listActiveRunBindings();\n for (const binding of bindings) await this.reconcileBinding(binding);\n }\n\n async reconcileBinding(binding: FactoryRunBindingRecord): Promise<void> {\n const reader = this.options.messageReader;\n if (!reader || binding.status !== 'active') return;\n // One keyed read up front: a binding whose item is gone or no longer in a\n // single rule stage would ingest nothing, so skip the cursor and message\n // reads entirely instead of paying them on every walk.\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!itemInRuleStage(this.options.boards, item)) return;\n const cursor = await this.options.storage.getToolResultCursor(binding.orgId, binding.factoryProjectId, binding.id);\n let page = 0;\n while (true) {\n const result = await reader.listMessages({\n threadId: binding.threadId,\n resourceId: binding.resourceId,\n page,\n perPage: TRANSCRIPT_PAGE_SIZE,\n ...(cursor ? { filter: { dateRange: { start: cursor.lastMessageCreatedAt } } } : {}),\n orderBy: { field: 'createdAt', direction: 'ASC' },\n });\n await this.ingestMessages(binding, result.messages, undefined, item);\n const last = result.messages.at(-1);\n if (last) {\n await this.options.storage.advanceToolResultCursor({\n bindingId: binding.id,\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n lastMessageId: last.id,\n lastMessageCreatedAt: last.createdAt instanceof Date ? last.createdAt : new Date(last.createdAt),\n updatedAt: new Date(),\n });\n }\n if (!result.hasMore) break;\n page += 1;\n }\n }\n\n private async ingestMessages(\n binding: FactoryRunBindingRecord,\n messages: MastraDBMessage[],\n toolCallIds?: ReadonlySet<string>,\n preloadedItem?: WorkItemRow,\n ): Promise<void> {\n const item = preloadedItem ?? (await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId }));\n if (!itemInRuleStage(this.options.boards, item)) return;\n for (const message of messages) {\n for (const toolResult of completedToolResults(message)) {\n if (toolCallIds && !toolCallIds.has(toolResult.toolCallId)) continue;\n try {\n await this.options.recordPullRequestProvenance?.({\n binding,\n item,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n toolName: toolResult.toolName,\n toolInput: toolResult.input,\n toolResult: toolResult.value,\n status: toolResult.status,\n });\n } catch {\n // Provenance is supporting evidence and must not block authoritative rule ingress.\n }\n await this.ingestToolResult(binding, item, toolResult);\n }\n }\n }\n\n private async ingestToolResult(\n binding: FactoryRunBindingRecord,\n item: WorkItemRow,\n toolResult: CompletedToolResult,\n ): Promise<void> {\n const board = boardForWorkItem(item);\n const rule = resolveBoardToolRule(this.options.boards, board, toolResult.toolName);\n if (!rule) return;\n const ingressId = JSON.stringify([\n binding.id,\n binding.threadId,\n toolResult.assistantMessageId,\n toolResult.toolCallId,\n ]);\n const prior = await this.options.storage.getTransitionResultByIngress(\n binding.orgId,\n binding.factoryProjectId,\n ingressId,\n );\n if (prior) return;\n const context: FactoryToolResultRuleContext = {\n tenant: { orgId: binding.orgId, projectId: binding.factoryProjectId },\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'toolResult', id: ingressId },\n cause: `Completed ${toolResult.toolName}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n item: {\n id: item.id,\n source: workItemSource(item.externalSource),\n sourceKey: workItemSourceKey(item),\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: item.stages,\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board,\n itemRevision: item.revision,\n toolName: toolResult.toolName,\n threadId: binding.threadId,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n result: { status: toolResult.status, value: toolResult.value },\n };\n\n let decision: FactoryRuleDecision | void = undefined;\n let decisions: FactoryCommitDecision[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = await withRuleTimeout(Promise.resolve(rule(Object.freeze(context))));\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n const validated = validateFactoryRuleDecisions([decision]);\n for (const entry of validated) assertFactoryDecisionTarget(entry, this.options.boards, board);\n decisions = validated;\n }\n } catch (error) {\n const timedOut = error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT';\n outcome = {\n status: 'rejected',\n code: timedOut ? 'timeout' : 'rule_error',\n reason: timedOut\n ? 'Factory rule evaluation timed out.'\n : error instanceof Error\n ? error.message.slice(0, 2_000)\n : 'Factory tool-result rule failed.',\n };\n }\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n ingress: { identity: ingressId, triggerType: 'tool.result' },\n configVersion: this.options.configVersion,\n expectedRevision: item.revision,\n actor: { ...context.actor },\n outcome,\n decisions: decisions.map(entry => ({ ...entry })),\n causalChain: [],\n now: new Date(),\n // The bound agent's own tool call carries the same consent as its governed moves.\n consentedBy: `agent:${binding.id}`,\n });\n if (committed.status !== 'committed' || !this.options.transitionService) return;\n for (const entry of decisions) {\n if (entry.type !== 'transition') continue;\n await this.options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n board: entry.board,\n stage: entry.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-tool-result-rule' },\n ingress: { type: 'rule', identity: `decision:${entry.idempotencyKey}` },\n cause: 'tool_result_rule',\n causalChain: [{ ingressId, decisionType: entry.type }],\n ...(isApprovedPlanResult(toolResult) ? { planApproved: true } : {}),\n });\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"processor.js","names":[],"sources":["../../src/rules/processor.ts"],"sourcesContent":["import { resolveRequestThinkingLevel } from '@mastra/code-sdk/agents/model';\nimport type { ThinkingLevel } from '@mastra/code-sdk/providers/openai-codex';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { MastraDBMessage, MessageList } from '@mastra/core/agent/message-list';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type {\n ComputeStateSignalArgs,\n ComputeStateSignalResult,\n ProcessInputArgs,\n ProcessInputStepArgs,\n Processor,\n} from '@mastra/core/processors';\n\nimport { boardForWorkItem, resolveBoardToolRule, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { toBoardSource } from '../boards/project-boards.js';\nimport type { ProjectBoardSource } from '../boards/project-boards.js';\nimport type { FactoryRunBindingRecord, WorkItemsStorage, WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { findSessionRunBinding, getFactorySessionCoordinates } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { workItemSource } from './types.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRuleStage,\n FactoryToolResultRuleContext,\n} from './types.js';\nimport {\n assertFactoryDecisionTarget,\n normalizeFactoryRuleJsonValue,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst STATE_ID = 'factory-phase';\nconst RULE_TIMEOUT_MS = 5_000;\nconst TRANSCRIPT_PAGE_SIZE = 50;\nconst MAX_LINKED_ITEMS = 5;\nfunction ruleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): string | undefined {\n return item && workItemPhaseSemantics(boards, item) ? item.stages[0] : undefined;\n}\n\nfunction itemInRuleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): item is WorkItemRow {\n return ruleStage(boards, item) !== undefined;\n}\n\ntype PersistedMessageReader = {\n listMessages(input: {\n threadId: string;\n resourceId?: string;\n page: number;\n perPage: number;\n filter?: { dateRange?: { start?: Date } };\n orderBy: { field: 'createdAt'; direction: 'ASC' };\n }): Promise<{ messages: MastraDBMessage[]; hasMore: boolean }>;\n};\n\ntype CompletedToolResult = {\n assistantMessageId: string;\n messageCreatedAt: Date;\n toolCallId: string;\n toolName: string;\n input: FactoryRuleJsonValue;\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n};\n\nfunction isApprovedPlanResult(result: CompletedToolResult): boolean {\n if (result.toolName !== 'submit_plan' || result.status !== 'success') return false;\n if (typeof result.value === 'string') return result.value.startsWith('Plan approved.');\n if (!result.value || typeof result.value !== 'object' || Array.isArray(result.value)) return false;\n const content = result.value.content;\n return typeof content === 'string' && content.startsWith('Plan approved.');\n}\n\ntype RuntimeSnapshot = {\n modelId: string;\n thinkingLevel: ThinkingLevel;\n};\n\ntype ActivePhaseSnapshotBase = {\n bindingId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n role: string;\n configVersion: string;\n status: 'active';\n};\n\ntype ActivePhaseSnapshotValue = ActivePhaseSnapshotBase & {\n board: string;\n modelId?: string;\n thinkingLevel?: ThinkingLevel;\n};\n\ntype PhaseSnapshotValue = ActivePhaseSnapshotValue | { bindingId?: string; status: 'none' };\n\nfunction reviewRuntimeFromRequestContext(\n requestContext: ComputeStateSignalArgs['requestContext'],\n defaultThinkingLevel: ThinkingLevel | undefined,\n): RuntimeSnapshot {\n if (!requestContext || typeof requestContext.get !== 'function') {\n throw new Error('Factory review phase requires a controller request context.');\n }\n const context = requestContext.get<'controller', AgentControllerRequestContext<MastraCodeState>>('controller');\n const modelId = context?.session?.modelId.trim();\n if (!modelId) {\n throw new Error('Factory review phase requires a selected session model.');\n }\n return {\n modelId,\n thinkingLevel: resolveRequestThinkingLevel({ state: context?.state }, undefined, defaultThinkingLevel),\n };\n}\n\nfunction workItemSourceKey(item: WorkItemRow): string | null {\n const source = item.externalSource;\n return source ? `${source.integrationId}:${source.type}:${source.externalId}` : null;\n}\n\nfunction boundedError(value: unknown): FactoryRuleJsonValue {\n const message = value instanceof Error ? value.message : typeof value === 'string' ? value : 'Tool execution failed.';\n return { message: message.slice(0, 2_000) };\n}\n\nfunction boundedResult(value: unknown): FactoryRuleJsonValue {\n try {\n return normalizeFactoryRuleJsonValue(value);\n } catch {\n return { message: 'Tool result was not serializable.' };\n }\n}\n\nfunction messageParts(message: MastraDBMessage): unknown[] {\n const content = message.content as { parts?: unknown[]; toolInvocations?: unknown[] } | unknown[] | undefined;\n if (Array.isArray(content)) return content;\n if (Array.isArray(content?.parts)) return content.parts;\n return Array.isArray(content?.toolInvocations) ? content.toolInvocations : [];\n}\n\nfunction completedStepToolCallIds(steps: unknown[]): Set<string> {\n const ids = new Set<string>();\n for (const rawStep of steps) {\n if (!rawStep || typeof rawStep !== 'object') continue;\n const toolResults = (rawStep as { toolResults?: unknown[] }).toolResults;\n if (!Array.isArray(toolResults)) continue;\n for (const rawResult of toolResults) {\n if (!rawResult || typeof rawResult !== 'object') continue;\n const toolCallId = (rawResult as { toolCallId?: unknown }).toolCallId;\n if (typeof toolCallId === 'string') ids.add(toolCallId);\n }\n }\n return ids;\n}\n\nfunction completedToolResults(message: MastraDBMessage): CompletedToolResult[] {\n if (message.role !== 'assistant') return [];\n const createdAt = message.createdAt instanceof Date ? message.createdAt : new Date(message.createdAt);\n const completed: CompletedToolResult[] = [];\n for (const rawPart of messageParts(message)) {\n if (!rawPart || typeof rawPart !== 'object') continue;\n const part = rawPart as Record<string, unknown>;\n const invocation =\n part.type === 'tool-invocation' && part.toolInvocation && typeof part.toolInvocation === 'object'\n ? (part.toolInvocation as Record<string, unknown>)\n : part;\n const state = invocation.state;\n if (state !== 'result' && state !== 'error') continue;\n const toolCallId = invocation.toolCallId;\n const toolName = invocation.toolName ?? invocation.name;\n if (typeof toolCallId !== 'string' || typeof toolName !== 'string') continue;\n completed.push({\n assistantMessageId: message.id,\n messageCreatedAt: createdAt,\n toolCallId,\n toolName: toolName.slice(0, 256),\n input: boundedResult(invocation.args ?? {}),\n status: state === 'error' ? 'error' : 'success',\n value: state === 'error' ? boundedError(invocation.result ?? invocation.error) : boundedResult(invocation.result),\n });\n }\n return completed;\n}\n\nfunction currentCompletedToolMessage(\n messages: MastraDBMessage[],\n toolCallIds: ReadonlySet<string>,\n): MastraDBMessage | undefined {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]!;\n if (completedToolResults(message).some(result => toolCallIds.has(result.toolCallId))) return message;\n }\n return undefined;\n}\n\nasync function phaseCacheKey(value: ActivePhaseSnapshotValue, linked: WorkItemRow[]): Promise<string> {\n const serialized = JSON.stringify({\n ...value,\n linked: linked.map(item => [item.id, item.revision, item.stages[0]]),\n });\n return Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(serialized))).toString(\n 'hex',\n );\n}\n\nfunction escapeText(value: string): string {\n return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');\n}\n\nfunction phaseFromSignal(signal: { metadata?: Record<string, unknown> } | undefined): PhaseSnapshotValue | undefined {\n return (signal?.metadata?.value as { phase?: PhaseSnapshotValue } | undefined)?.phase;\n}\n\nfunction latestPhase(args: ComputeStateSignalArgs): PhaseSnapshotValue | undefined {\n for (const signal of [...args.activeStateSignals].reverse()) {\n const phase = phaseFromSignal(signal);\n if (phase) return phase;\n }\n return phaseFromSignal(args.lastSnapshot);\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nconst reportedUnavailableMemorySettings = new WeakSet<object>();\n\n/**\n * Marks this run's Factory memory settings as unavailable so memory falls back\n * to auto, and tells the thread once per request that saved choices were not\n * applied. The reserved request-context key keeps clients from spoofing it.\n */\nexport function reportMemorySettingsUnavailable(\n requestContext: NonNullable<ProcessInputArgs['requestContext']>,\n reason: string,\n): void {\n requestContext.set('mastra__factoryMemorySettings', { status: 'unavailable', reason });\n console.warn('[Factory Memory Settings] Failed to load settings for run', { error: reason });\n if (reportedUnavailableMemorySettings.has(requestContext)) return;\n reportedUnavailableMemorySettings.add(requestContext);\n const controller = requestContext.get('controller') as\n | Pick<AgentControllerRequestContext<MastraCodeState>, 'emitEvent'>\n | undefined;\n controller?.emitEvent?.({\n type: 'error',\n error: new Error(\n 'Memory settings could not be loaded for this run, so observational memory is using Auto models and default thresholds.',\n ),\n errorType: 'factory_memory_settings_unavailable',\n retryable: false,\n });\n}\n\nexport class FactoryPhaseStateProcessor implements Processor<'factory-phase'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n /** Request contexts whose caller row is already resolved for this request. */\n private readonly loadedMemorySettings = new WeakSet<object>();\n private readonly boards: ProjectBoardSource;\n\n constructor(\n private readonly options: {\n configVersion: string;\n storage: WorkItemsStorage;\n boards: BoardRegistry | ProjectBoardSource;\n loadMemorySettings?: (args: {\n requestContext: ProcessInputStepArgs['requestContext'];\n binding: FactoryRunBindingRecord | null;\n }) => Promise<void>;\n /** The session's routed thinking default, used when the session has not picked a level. */\n defaultThinkingLevel?: (requestContext: ComputeStateSignalArgs['requestContext']) => Promise<ThinkingLevel>;\n transitionService?: Pick<FactoryTransitionService, 'transition'>;\n messageReader?: PersistedMessageReader;\n recordPullRequestProvenance?: (input: {\n binding: FactoryRunBindingRecord;\n item: WorkItemRow;\n assistantMessageId: string;\n toolCallId: string;\n toolName: string;\n toolInput: FactoryRuleJsonValue;\n toolResult: FactoryRuleJsonValue;\n status: 'success' | 'error';\n }) => Promise<void>;\n },\n ) {\n this.boards = toBoardSource(options.boards);\n }\n\n /**\n * Memory settings are caller state, not run state. Factory invokes this while\n * resolving its configured processor list, before Agent resolves dynamic\n * Memory and enumerates Memory's processors. This guarantees both the Memory\n * options and its model callbacks see the authoritative row for this\n * invocation. Doing this in `processInputStep` would be too late: Memory's\n * input-step hook runs before configured processors.\n */\n async prepareMemorySettings(requestContext: ProcessInputArgs['requestContext']): Promise<void> {\n await this.loadCallerMemorySettings(requestContext, undefined, true);\n }\n\n async processInput(args: ProcessInputArgs): Promise<MessageList> {\n await this.loadCallerMemorySettings(args.requestContext);\n return args.messageList;\n }\n\n async processInputStep(args: ProcessInputStepArgs): Promise<MessageList | undefined> {\n const address = getFactorySessionCoordinates(args.requestContext);\n let binding: FactoryRunBindingRecord | null | undefined;\n try {\n binding = address ? await this.options.storage.findRunBindingBySession(address) : undefined;\n } catch {\n await this.loadCallerMemorySettings(args.requestContext, undefined);\n return;\n }\n // Resumed runs skip the input-processor phase, so re-check here: it is a\n // no-op once `processInput` already placed the row for this request. Passing\n // `undefined` (not `null`) keeps the recovery lookup inside the loader, so a\n // recovered session reaches its project row from this hook too.\n await this.loadCallerMemorySettings(args.requestContext, binding);\n if (!binding || binding.status !== 'active') return;\n const completedToolCallIds = completedStepToolCallIds(args.steps);\n const completedMessage = currentCompletedToolMessage(args.messages, completedToolCallIds);\n if (completedMessage) {\n await this.ingestMessages(binding, [completedMessage], completedToolCallIds);\n }\n }\n\n /**\n * Loads the caller's row once per invocation. The input hook always refreshes\n * it in case the controller reused a request context; later step hooks reuse\n * that value (`undefined` means it has not been loaded yet; `null` means an\n * empty row).\n *\n * The WeakSet covers loaders that do not set the context key themselves, so a\n * request is still loaded once. A failed load records an unavailable sentinel\n * (memory then runs both roles on auto with default thresholds) and is not\n * marked loaded so the next step can retry.\n */\n private async loadCallerMemorySettings(\n requestContext: ProcessInputStepArgs['requestContext'] | ProcessInputArgs['requestContext'],\n binding?: FactoryRunBindingRecord | null,\n force = false,\n ): Promise<void> {\n if (!requestContext || !this.options.loadMemorySettings) return;\n const currentSettings = requestContext.get('mastra__factoryMemorySettings') as\n | { status?: unknown }\n | null\n | undefined;\n if (force) this.loadedMemorySettings.delete(requestContext);\n if (!force && currentSettings !== undefined && currentSettings?.status !== 'unavailable') return;\n if (!force && this.loadedMemorySettings.has(requestContext)) return;\n try {\n let runBinding = binding ?? null;\n if (binding === undefined) {\n const address = getFactorySessionCoordinates(requestContext);\n runBinding = address\n ? await this.options.storage.findRunBindingBySession(address)\n : // Crash-recovered sessions come back with empty state, so the binding\n // table is the last source of the project whose row applies. Without\n // this the run would silently read the caller's personal row.\n await findSessionRunBinding(requestContext, this.options.storage);\n }\n await this.options.loadMemorySettings({ requestContext, binding: runBinding });\n this.loadedMemorySettings.add(requestContext);\n } catch (error) {\n reportMemorySettingsUnavailable(requestContext, error instanceof Error ? error.message : String(error));\n }\n }\n\n async computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult> {\n const address = getFactorySessionCoordinates(args.requestContext);\n if (!address) return;\n const binding = await this.options.storage.findRunBindingBySession(address);\n const prior = latestPhase(args);\n const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;\n\n if (!binding) return;\n if (binding.status !== 'active') {\n if (!hasBase || prior?.status !== 'active') return;\n return {\n id: STATE_ID,\n cacheKey: `factory:none:${prior.bindingId ?? 'revoked'}`,\n mode: 'snapshot',\n tagName: 'factory-phase',\n contents: '\\n',\n value: { phase: { status: 'none' } },\n attributes: { status: 'none' },\n metadata: { value: { phase: { status: 'none' } } },\n };\n }\n\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const boards = await this.boards.load(binding);\n const stage = ruleStage(boards, item);\n if (!item || !stage) return;\n const allItems = await this.options.storage.list({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n });\n const linked = allItems\n .filter(candidate => candidate.parentWorkItemId === item.id || item.parentWorkItemId === candidate.id)\n .slice(0, MAX_LINKED_ITEMS);\n const board = boardForWorkItem(item);\n const baseValue: ActivePhaseSnapshotBase = {\n status: 'active',\n bindingId: binding.id,\n itemId: item.id,\n revision: item.revision,\n stage,\n role: binding.role,\n configVersion: this.options.configVersion,\n };\n const value: ActivePhaseSnapshotValue =\n board === 'review'\n ? {\n ...baseValue,\n board,\n ...reviewRuntimeFromRequestContext(\n args.requestContext,\n await this.options.defaultThinkingLevel?.(args.requestContext),\n ),\n }\n : { ...baseValue, board };\n const cacheKey = await phaseCacheKey(value, linked);\n if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey)\n return;\n\n const linkedText = linked.length\n ? `\\nLinked items: ${linked.map(candidate => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join('; ')}`\n : '';\n const phase = boards.get(board)?.phases[stage];\n const phaseLabel = phase?.name ?? stage;\n const exitText =\n phase?.kind === 'working' && phase.exitTool\n ? `This phase ends when ${escapeText(phase.exitTool.id)} succeeds. Then move the card with factory_transition_work_item to one of: ${phase.nextPhases.map(escapeText).join(', ')}.\\n`\n : '';\n const runtime =\n value.modelId && value.thinkingLevel ? { modelId: value.modelId, thinkingLevel: value.thinkingLevel } : null;\n const snapshotContents =\n `Factory ${board} phase: ${escapeText(phaseLabel)} (${escapeText(stage)})\\n` +\n `Work item: ${escapeText(item.title)} (${item.id})\\n` +\n `Role: ${escapeText(binding.role)}\\nRevision: ${item.revision}\\nConfig: ${escapeText(this.options.configVersion)}\\n` +\n (runtime\n ? `Runtime: model=${escapeText(runtime.modelId)}, reasoning-setting=${escapeText(runtime.thinkingLevel)}\\n`\n : '') +\n ((board === 'work' || board === 'review') && stage === 'intake'\n ? 'This card rests in Intake: its work is paused. Answer questions without moving it; when the user asks to resume, request the transition into the working stage first, then continue the work in this session.\\n'\n : '') +\n exitText +\n `Use factory_transition_work_item with expectedRevision ${item.revision} to request a phase change.${escapeText(linkedText)}`;\n const isDelta = hasBase && prior?.status === 'active';\n return {\n id: STATE_ID,\n cacheKey,\n mode: isDelta ? 'delta' : 'snapshot',\n tagName: 'factory-phase',\n contents: isDelta ? `Factory phase update:\\n${snapshotContents}` : snapshotContents,\n value: { phase: value },\n ...(isDelta ? { delta: { phase: value } } : {}),\n attributes: {\n status: 'active',\n board,\n stage,\n role: binding.role,\n revision: item.revision,\n ...(runtime ?? {}),\n },\n metadata: { value: { phase: value } },\n };\n }\n\n async reconcileAllBoundThreads(): Promise<void> {\n if (!this.options.messageReader) return;\n const bindings = await this.options.storage.listActiveRunBindings();\n for (const binding of bindings) await this.reconcileBinding(binding);\n }\n\n async reconcileBinding(binding: FactoryRunBindingRecord): Promise<void> {\n const reader = this.options.messageReader;\n if (!reader || binding.status !== 'active') return;\n // One keyed read up front: a binding whose item is gone or no longer in a\n // single rule stage would ingest nothing, so skip the cursor and message\n // reads entirely instead of paying them on every walk.\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!itemInRuleStage(await this.boards.load(binding), item)) return;\n const cursor = await this.options.storage.getToolResultCursor(binding.orgId, binding.factoryProjectId, binding.id);\n let page = 0;\n while (true) {\n const result = await reader.listMessages({\n threadId: binding.threadId,\n resourceId: binding.resourceId,\n page,\n perPage: TRANSCRIPT_PAGE_SIZE,\n ...(cursor ? { filter: { dateRange: { start: cursor.lastMessageCreatedAt } } } : {}),\n orderBy: { field: 'createdAt', direction: 'ASC' },\n });\n await this.ingestMessages(binding, result.messages, undefined, item);\n const last = result.messages.at(-1);\n if (last) {\n await this.options.storage.advanceToolResultCursor({\n bindingId: binding.id,\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n lastMessageId: last.id,\n lastMessageCreatedAt: last.createdAt instanceof Date ? last.createdAt : new Date(last.createdAt),\n updatedAt: new Date(),\n });\n }\n if (!result.hasMore) break;\n page += 1;\n }\n }\n\n private async ingestMessages(\n binding: FactoryRunBindingRecord,\n messages: MastraDBMessage[],\n toolCallIds?: ReadonlySet<string>,\n preloadedItem?: WorkItemRow,\n ): Promise<void> {\n const item = preloadedItem ?? (await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId }));\n if (!itemInRuleStage(await this.boards.load(binding), item)) return;\n for (const message of messages) {\n for (const toolResult of completedToolResults(message)) {\n if (toolCallIds && !toolCallIds.has(toolResult.toolCallId)) continue;\n try {\n await this.options.recordPullRequestProvenance?.({\n binding,\n item,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n toolName: toolResult.toolName,\n toolInput: toolResult.input,\n toolResult: toolResult.value,\n status: toolResult.status,\n });\n } catch {\n // Provenance is supporting evidence and must not block authoritative rule ingress.\n }\n await this.ingestToolResult(binding, item, toolResult);\n }\n }\n }\n\n private async ingestToolResult(\n binding: FactoryRunBindingRecord,\n item: WorkItemRow,\n toolResult: CompletedToolResult,\n ): Promise<void> {\n const board = boardForWorkItem(item);\n const boards = await this.boards.load(binding);\n const rule = resolveBoardToolRule(boards, board, toolResult.toolName);\n if (!rule) return;\n const ingressId = JSON.stringify([\n binding.id,\n binding.threadId,\n toolResult.assistantMessageId,\n toolResult.toolCallId,\n ]);\n const prior = await this.options.storage.getTransitionResultByIngress(\n binding.orgId,\n binding.factoryProjectId,\n ingressId,\n );\n if (prior) return;\n const context: FactoryToolResultRuleContext = {\n tenant: { orgId: binding.orgId, projectId: binding.factoryProjectId },\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'toolResult', id: ingressId },\n cause: `Completed ${toolResult.toolName}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n item: {\n id: item.id,\n source: workItemSource(item.externalSource),\n sourceKey: workItemSourceKey(item),\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: item.stages,\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board,\n itemRevision: item.revision,\n toolName: toolResult.toolName,\n threadId: binding.threadId,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n result: { status: toolResult.status, value: toolResult.value },\n };\n\n let decision: FactoryRuleDecision | void = undefined;\n let decisions: FactoryCommitDecision[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = await withRuleTimeout(Promise.resolve(rule(Object.freeze(context))));\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n const validated = validateFactoryRuleDecisions([decision]);\n for (const entry of validated) assertFactoryDecisionTarget(entry, boards, board);\n decisions = validated;\n }\n } catch (error) {\n const timedOut = error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT';\n outcome = {\n status: 'rejected',\n code: timedOut ? 'timeout' : 'rule_error',\n reason: timedOut\n ? 'Factory rule evaluation timed out.'\n : error instanceof Error\n ? error.message.slice(0, 2_000)\n : 'Factory tool-result rule failed.',\n };\n }\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n ingress: { identity: ingressId, triggerType: 'tool.result' },\n configVersion: this.options.configVersion,\n expectedRevision: item.revision,\n actor: { ...context.actor },\n outcome,\n decisions: decisions.map(entry => ({ ...entry })),\n causalChain: [],\n now: new Date(),\n // The bound agent's own tool call carries the same consent as its governed moves.\n consentedBy: `agent:${binding.id}`,\n });\n if (committed.status !== 'committed' || !this.options.transitionService) return;\n for (const entry of decisions) {\n if (entry.type !== 'transition') continue;\n await this.options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n board: entry.board,\n stage: entry.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-tool-result-rule' },\n ingress: { type: 'rule', identity: `decision:${entry.idempotencyKey}` },\n cause: 'tool_result_rule',\n causalChain: [{ ingressId, decisionType: entry.type }],\n ...(isApprovedPlanResult(toolResult) ? { planApproved: true } : {}),\n });\n }\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { RequestContext } from '@mastra/core/request-context';
2
2
  import type { BoardRegistry } from '../boards/index.js';
3
+ import type { ProjectBoardSource } from '../boards/project-boards.js';
3
4
  import type { IntegrationTools } from '../integrations/base.js';
4
5
  import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
5
6
  import type { FactorySessionSourceLookup } from './binding-context.js';
@@ -9,7 +10,7 @@ export declare function createFactoryTransitionTools(options: {
9
10
  storage: WorkItemsStorage;
10
11
  transitionService: Pick<FactoryTransitionService, 'transition'>;
11
12
  sessions?: FactorySessionSourceLookup;
12
- boards?: BoardRegistry;
13
+ boards?: BoardRegistry | ProjectBoardSource;
13
14
  }): Promise<IntegrationTools>;
14
15
  export declare const REVIEW_VERDICTS: readonly ['approve', 'request changes'];
15
16
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AA8BxE,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqF5B;AAED,eAAO,MAAM,eAAe,YAAI,SAAS,EAAE,iBAAiB,CAAU,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AA8BxE,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,MAAM,CAAC,EAAE,aAAa,GAAG,kBAAkB,CAAC;CAC7C,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwF5B;AAED,eAAO,MAAM,eAAe,YAAI,SAAS,EAAE,iBAAiB,CAAU,CAAC"}
@@ -3,6 +3,7 @@ import { BOARD_IDENTIFIER_RE } from "./validation.js";
3
3
  import { boardForWorkItem, workItemPhaseSemantics } from "../boards/semantics.js";
4
4
  import "../boards/work.js";
5
5
  import "../boards/index.js";
6
+ import { toBoardSource } from "../boards/project-boards.js";
6
7
  import { WorkItemUpdateConflictError } from "../storage/domains/work-items/base.js";
7
8
  import { resolveFactorySessionAddress } from "./binding-context.js";
8
9
  import { z } from "zod";
@@ -31,7 +32,7 @@ async function createFactoryTransitionTools(options) {
31
32
  orgId: retired.orgId,
32
33
  id: retired.workItemId
33
34
  });
34
- if (item && workItemPhaseSemantics(options.boards, item)?.kind === "terminal") return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };
35
+ if (item && workItemPhaseSemantics(await toBoardSource(options.boards).load(retired), item)?.kind === "terminal") return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };
35
36
  }
36
37
  return {};
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { SELF_REVIEW_ROUND_LIMIT } from '../boards/work.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { WorkItemUpdateConflictError } from '../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactorySessionSourceLookup } from './binding-context.js';\nimport { resolveFactorySessionAddress } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { FACTORY_TRIAGE_TYPES } from './types.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from './validation.js';\n\nconst MAX_RATIONALE_LENGTH = 1_000;\n\nconst transitionInputSchema = z\n .object({\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n expectedRevision: z.number().int().positive(),\n // Providers strip maxLength from the JSON schema and models can't count characters, so a\n // hard cap invites overshoot-retry loops at the end of every run. Accept and clamp instead.\n rationale: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`,\n ),\n // Sessions are shared across role rotations, so a non-triage agent sees the triage\n // agent's earlier call (with triageType) in its history and copies the shape. Accept\n // the key here and drop it in execute rather than fail the whole call on it.\n triageType: z.enum(FACTORY_TRIAGE_TYPES).optional(),\n })\n .strict();\n\nconst triageTransitionInputSchema = transitionInputSchema.extend({\n triageType: z.enum(FACTORY_TRIAGE_TYPES),\n});\n\nexport async function createFactoryTransitionTools(options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n boards?: BoardRegistry;\n}): Promise<IntegrationTools> {\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const availableBinding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n if (!availableBinding) {\n // No live binding for this session. A resumed session whose binding was\n // revoked when its work item reached a terminal stage was retired out from\n // under the model: rather than silently drop the tool — which leaves the\n // resumed run to rationalize a false blocked state — surface a tool that\n // fails with the concrete reason. Threads that were never bound (or whose\n // binding was rotated to a peer role, leaving the item still in flight) get\n // no tool as before.\n const retired = await options.storage.findRunBindingBySession(resolution.address);\n if (retired && retired.status === 'revoked' && options.boards) {\n const item = await options.storage.get({ orgId: retired.orgId, id: retired.workItemId });\n if (item && workItemPhaseSemantics(options.boards, item)?.kind === 'terminal') {\n return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };\n }\n }\n return {};\n }\n let isTriage = false;\n if (availableBinding.role === 'triage') {\n const item = await options.storage.get({ orgId: availableBinding.orgId, id: availableBinding.workItemId });\n if (!item) return {};\n isTriage = boardForWorkItem(item) === 'work';\n }\n\n return {\n ...(availableBinding.role === 'review' ? createReviewVerdictTool(options, availableBinding.workItemId) : {}),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: isTriage\n ? 'Report the triage classification and request a governed stage transition for the Factory work item exactly bound to this thread. Only bugs may request Planning autonomously; closure outcomes may request a terminal stage. Feature requests and other non-bug classifications that remain open must stay in their current Intake or Triage stage for maintainer approval.'\n : 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',\n inputSchema: isTriage ? triageTransitionInputSchema : transitionInputSchema,\n requireApproval: true,\n execute: async ({ stage, expectedRevision, rationale, triageType: requestedTriageType }, execution) => {\n const currentResolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const currentAddress = currentResolution?.address ?? null;\n const toolCallId = execution.agent?.toolCallId;\n if (!currentAddress || !toolCallId) {\n throw new Error('Factory transitions require an authenticated bound agent tool call.');\n }\n const binding = await options.storage.findActiveRunBinding(currentAddress);\n // Authority is the work item this session is bound to, not the individual\n // binding row. Handing the next role its turn in an existing session\n // rotates the binding, and tools built for the previous role stay live\n // across that rotation; keying on row identity would strand the run that\n // the rotation exists to start. Re-pointing a session at a different item\n // is the hijack this guards against.\n if (!binding || binding.workItemId !== availableBinding.workItemId) {\n throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n const board = boardForWorkItem(item);\n // Only a Work triage binding may classify; other roles can echo the key from history.\n const triageType = board === 'work' && binding.role === 'triage' ? requestedTriageType : undefined;\n\n const result = await options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board,\n stage,\n expectedRevision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },\n cause: rationale,\n ...(triageType ? { triageType } : {}),\n });\n\n return result;\n },\n }),\n };\n}\n\nexport const REVIEW_VERDICTS = ['approve', 'request changes'] as const;\n\nconst MAX_SELF_REVIEW_FINDINGS_LENGTH = 20_000;\n\nconst reviewVerdictInputSchema = z.object({\n verdict: z.enum(REVIEW_VERDICTS),\n reviewedHeadSha: z\n .string()\n .regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i, 'Use the full commit SHA of the reviewed head.'),\n findings: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_SELF_REVIEW_FINDINGS_LENGTH\n ? value\n : `${value.slice(0, MAX_SELF_REVIEW_FINDINGS_LENGTH - 1)}…`,\n )\n .optional()\n .describe(\n 'Self-review only: the findings and test report, in Markdown. On request changes the builder receives them; on approve they go in the change request body.',\n ),\n});\n\n/**\n * A review pass ends by recording its verdict on the card, which stays in\n * Reviewing: Done is reserved for the merge, and a verdict is not a lane. The\n * next push re-reviews from the recorded verdict.\n */\nfunction createReviewVerdictTool(\n options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n },\n boundWorkItemId: string,\n): IntegrationTools {\n return {\n factory_record_review_verdict: createTool({\n id: 'factory_record_review_verdict',\n description:\n 'Record the review verdict and the head SHA it covers on the card bound to this thread, ending the review pass. On a Review card it records the published PR verdict and the card stays in Reviewing. On a Work card in Review with no change request open yet (self-review), approve keeps the card in Review and wakes the builder to open the change request; request changes sends it back to Building with your findings.',\n inputSchema: reviewVerdictInputSchema,\n requireApproval: false,\n execute: async ({ verdict, reviewedHeadSha, findings }, execution) => {\n const resolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const binding = resolution ? await options.storage.findActiveRunBinding(resolution.address) : null;\n if (!binding || binding.workItemId !== boundWorkItemId || binding.role !== 'review') {\n throw new Error('Factory review binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n if (\n boardForWorkItem(item) === 'work' &&\n item.stages[0] === 'review' &&\n typeof item.metadata?.openPullRequestNumber !== 'number'\n ) {\n return recordSelfReviewVerdict({\n storage: options.storage,\n transitionService: options.transitionService,\n binding,\n item,\n verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n findings,\n toolCallId: execution.agent?.toolCallId,\n });\n }\n if (boardForWorkItem(item) !== 'review' || item.stages[0] !== 'review') {\n throw new Error(`Only a card in Reviewing records a verdict; this one is in ${item.stages.join(', ')}.`);\n }\n const reviewedAt = new Date().toISOString();\n // Revision-checked, so a merge that moves the card between the stage\n // check and this write fails the call instead of stamping a Done card.\n try {\n await options.storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n ...(item.metadata ?? {}),\n reviewVerdict: verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n reviewedAt,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error(\n 'The Review card changed while recording the verdict; call again to record it on the current card.',\n );\n }\n // Mirror the verdict onto the Work item that authored the PR, so its\n // builder cannot close the work while changes are still requested.\n const parent = item.parentWorkItemId\n ? await options.storage.get({ orgId: binding.orgId, id: item.parentWorkItemId })\n : null;\n if (parent && boardForWorkItem(parent) === 'work') {\n await options.storage.update({\n orgId: binding.orgId,\n id: parent.id,\n userId: `agent:${binding.id}`,\n patch: {\n metadata: { reviewVerdict: verdict, reviewedHeadSha: reviewedHeadSha.toLowerCase(), reviewedAt },\n },\n });\n }\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt };\n },\n }),\n };\n}\n\n/**\n * Self-review ends the same way a PR review does, but on the Work card before\n * any change request exists, and it moves the card itself: the verdict is the\n * only thing that decides where the work goes next.\n */\nasync function recordSelfReviewVerdict(input: {\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n binding: { id: string; orgId: string; factoryProjectId: string; workItemId: string; role: string };\n item: WorkItemRow;\n verdict: (typeof REVIEW_VERDICTS)[number];\n reviewedHeadSha: string;\n findings: string | undefined;\n toolCallId: string | undefined;\n}) {\n const { storage, binding, item, verdict, reviewedHeadSha, findings } = input;\n if (!input.toolCallId) throw new Error('Self-review verdicts require an authenticated bound agent tool call.');\n const reviewedAt = new Date().toISOString();\n const previousRounds = typeof item.metadata?.selfReviewRounds === 'number' ? item.metadata.selfReviewRounds : 0;\n const rounds = verdict === 'request changes' ? previousRounds + 1 : previousRounds;\n // Every SELF_REVIEW_ROUND_LIMIT rounds the card rests in Intake for a person instead of looping;\n // moving it back to Building grants another full set of rounds.\n const parked = verdict === 'request changes' && rounds % SELF_REVIEW_ROUND_LIMIT === 0;\n let updated: Awaited<ReturnType<WorkItemsStorage['update']>>;\n try {\n updated = await storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n selfReviewVerdict: verdict,\n selfReviewedHeadSha: reviewedHeadSha,\n selfReviewedAt: reviewedAt,\n selfReviewFindings: findings ?? null,\n selfReviewRounds: rounds,\n selfReviewNeedsHuman: parked,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error('The Work card changed while recording the verdict; call again to record it on the current card.');\n }\n if (!updated) throw new Error('Bound Factory work item not found.');\n if (verdict === 'approve')\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds };\n const transition = await input.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board: 'work',\n stage: parked ? 'intake' : 'execute',\n expectedRevision: updated.item.revision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${input.toolCallId}` },\n cause: parked\n ? `Self-review requested changes ${rounds} times; parked for a person to decide how to proceed.`\n : `Self-review requested changes on ${reviewedHeadSha} (round ${rounds}).`,\n });\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds, transition };\n}\n\n/**\n * A retired session's transition tool: it exists only to fail loudly. The run's\n * binding was revoked when its work item settled at a terminal stage, so there\n * is nothing to transition. Registering it (instead of dropping the tool) stops\n * a resumed model from inventing a false \"blocked\" state when the affordance it\n * expects has silently vanished.\n */\nfunction createRetiredSessionTool(workItemId: string, stage: string | undefined): IntegrationTools[string] {\n return createTool({\n id: 'factory_transition_work_item',\n description:\n 'This Factory session has been retired: its work item already reached a terminal stage and the run binding was revoked. No further transitions can be requested from this session.',\n inputSchema: transitionInputSchema,\n requireApproval: false,\n execute: async () => {\n throw new Error(\n `This Factory session has been retired: work item ${workItemId} reached a terminal stage${\n stage ? ` (${stage})` : ''\n } and its run binding was revoked. No transition can be requested from this session.`,\n );\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { toBoardSource } from '../boards/project-boards.js';\nimport type { ProjectBoardSource } from '../boards/project-boards.js';\nimport { SELF_REVIEW_ROUND_LIMIT } from '../boards/work.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { WorkItemUpdateConflictError } from '../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactorySessionSourceLookup } from './binding-context.js';\nimport { resolveFactorySessionAddress } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { FACTORY_TRIAGE_TYPES } from './types.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from './validation.js';\n\nconst MAX_RATIONALE_LENGTH = 1_000;\n\nconst transitionInputSchema = z\n .object({\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n expectedRevision: z.number().int().positive(),\n // Providers strip maxLength from the JSON schema and models can't count characters, so a\n // hard cap invites overshoot-retry loops at the end of every run. Accept and clamp instead.\n rationale: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`,\n ),\n // Sessions are shared across role rotations, so a non-triage agent sees the triage\n // agent's earlier call (with triageType) in its history and copies the shape. Accept\n // the key here and drop it in execute rather than fail the whole call on it.\n triageType: z.enum(FACTORY_TRIAGE_TYPES).optional(),\n })\n .strict();\n\nconst triageTransitionInputSchema = transitionInputSchema.extend({\n triageType: z.enum(FACTORY_TRIAGE_TYPES),\n});\n\nexport async function createFactoryTransitionTools(options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n boards?: BoardRegistry | ProjectBoardSource;\n}): Promise<IntegrationTools> {\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const availableBinding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n if (!availableBinding) {\n // No live binding for this session. A resumed session whose binding was\n // revoked when its work item reached a terminal stage was retired out from\n // under the model: rather than silently drop the tool — which leaves the\n // resumed run to rationalize a false blocked state — surface a tool that\n // fails with the concrete reason. Threads that were never bound (or whose\n // binding was rotated to a peer role, leaving the item still in flight) get\n // no tool as before.\n const retired = await options.storage.findRunBindingBySession(resolution.address);\n if (retired && retired.status === 'revoked' && options.boards) {\n const item = await options.storage.get({ orgId: retired.orgId, id: retired.workItemId });\n if (\n item &&\n workItemPhaseSemantics(await toBoardSource(options.boards).load(retired), item)?.kind === 'terminal'\n ) {\n return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };\n }\n }\n return {};\n }\n let isTriage = false;\n if (availableBinding.role === 'triage') {\n const item = await options.storage.get({ orgId: availableBinding.orgId, id: availableBinding.workItemId });\n if (!item) return {};\n isTriage = boardForWorkItem(item) === 'work';\n }\n\n return {\n ...(availableBinding.role === 'review' ? createReviewVerdictTool(options, availableBinding.workItemId) : {}),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: isTriage\n ? 'Report the triage classification and request a governed stage transition for the Factory work item exactly bound to this thread. Only bugs may request Planning autonomously; closure outcomes may request a terminal stage. Feature requests and other non-bug classifications that remain open must stay in their current Intake or Triage stage for maintainer approval.'\n : 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',\n inputSchema: isTriage ? triageTransitionInputSchema : transitionInputSchema,\n requireApproval: true,\n execute: async ({ stage, expectedRevision, rationale, triageType: requestedTriageType }, execution) => {\n const currentResolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const currentAddress = currentResolution?.address ?? null;\n const toolCallId = execution.agent?.toolCallId;\n if (!currentAddress || !toolCallId) {\n throw new Error('Factory transitions require an authenticated bound agent tool call.');\n }\n const binding = await options.storage.findActiveRunBinding(currentAddress);\n // Authority is the work item this session is bound to, not the individual\n // binding row. Handing the next role its turn in an existing session\n // rotates the binding, and tools built for the previous role stay live\n // across that rotation; keying on row identity would strand the run that\n // the rotation exists to start. Re-pointing a session at a different item\n // is the hijack this guards against.\n if (!binding || binding.workItemId !== availableBinding.workItemId) {\n throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n const board = boardForWorkItem(item);\n // Only a Work triage binding may classify; other roles can echo the key from history.\n const triageType = board === 'work' && binding.role === 'triage' ? requestedTriageType : undefined;\n\n const result = await options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board,\n stage,\n expectedRevision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },\n cause: rationale,\n ...(triageType ? { triageType } : {}),\n });\n\n return result;\n },\n }),\n };\n}\n\nexport const REVIEW_VERDICTS = ['approve', 'request changes'] as const;\n\nconst MAX_SELF_REVIEW_FINDINGS_LENGTH = 20_000;\n\nconst reviewVerdictInputSchema = z.object({\n verdict: z.enum(REVIEW_VERDICTS),\n reviewedHeadSha: z\n .string()\n .regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i, 'Use the full commit SHA of the reviewed head.'),\n findings: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_SELF_REVIEW_FINDINGS_LENGTH\n ? value\n : `${value.slice(0, MAX_SELF_REVIEW_FINDINGS_LENGTH - 1)}…`,\n )\n .optional()\n .describe(\n 'Self-review only: the findings and test report, in Markdown. On request changes the builder receives them; on approve they go in the change request body.',\n ),\n});\n\n/**\n * A review pass ends by recording its verdict on the card, which stays in\n * Reviewing: Done is reserved for the merge, and a verdict is not a lane. The\n * next push re-reviews from the recorded verdict.\n */\nfunction createReviewVerdictTool(\n options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n },\n boundWorkItemId: string,\n): IntegrationTools {\n return {\n factory_record_review_verdict: createTool({\n id: 'factory_record_review_verdict',\n description:\n 'Record the review verdict and the head SHA it covers on the card bound to this thread, ending the review pass. On a Review card it records the published PR verdict and the card stays in Reviewing. On a Work card in Review with no change request open yet (self-review), approve keeps the card in Review and wakes the builder to open the change request; request changes sends it back to Building with your findings.',\n inputSchema: reviewVerdictInputSchema,\n requireApproval: false,\n execute: async ({ verdict, reviewedHeadSha, findings }, execution) => {\n const resolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const binding = resolution ? await options.storage.findActiveRunBinding(resolution.address) : null;\n if (!binding || binding.workItemId !== boundWorkItemId || binding.role !== 'review') {\n throw new Error('Factory review binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n if (\n boardForWorkItem(item) === 'work' &&\n item.stages[0] === 'review' &&\n typeof item.metadata?.openPullRequestNumber !== 'number'\n ) {\n return recordSelfReviewVerdict({\n storage: options.storage,\n transitionService: options.transitionService,\n binding,\n item,\n verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n findings,\n toolCallId: execution.agent?.toolCallId,\n });\n }\n if (boardForWorkItem(item) !== 'review' || item.stages[0] !== 'review') {\n throw new Error(`Only a card in Reviewing records a verdict; this one is in ${item.stages.join(', ')}.`);\n }\n const reviewedAt = new Date().toISOString();\n // Revision-checked, so a merge that moves the card between the stage\n // check and this write fails the call instead of stamping a Done card.\n try {\n await options.storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n ...(item.metadata ?? {}),\n reviewVerdict: verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n reviewedAt,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error(\n 'The Review card changed while recording the verdict; call again to record it on the current card.',\n );\n }\n // Mirror the verdict onto the Work item that authored the PR, so its\n // builder cannot close the work while changes are still requested.\n const parent = item.parentWorkItemId\n ? await options.storage.get({ orgId: binding.orgId, id: item.parentWorkItemId })\n : null;\n if (parent && boardForWorkItem(parent) === 'work') {\n await options.storage.update({\n orgId: binding.orgId,\n id: parent.id,\n userId: `agent:${binding.id}`,\n patch: {\n metadata: { reviewVerdict: verdict, reviewedHeadSha: reviewedHeadSha.toLowerCase(), reviewedAt },\n },\n });\n }\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt };\n },\n }),\n };\n}\n\n/**\n * Self-review ends the same way a PR review does, but on the Work card before\n * any change request exists, and it moves the card itself: the verdict is the\n * only thing that decides where the work goes next.\n */\nasync function recordSelfReviewVerdict(input: {\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n binding: { id: string; orgId: string; factoryProjectId: string; workItemId: string; role: string };\n item: WorkItemRow;\n verdict: (typeof REVIEW_VERDICTS)[number];\n reviewedHeadSha: string;\n findings: string | undefined;\n toolCallId: string | undefined;\n}) {\n const { storage, binding, item, verdict, reviewedHeadSha, findings } = input;\n if (!input.toolCallId) throw new Error('Self-review verdicts require an authenticated bound agent tool call.');\n const reviewedAt = new Date().toISOString();\n const previousRounds = typeof item.metadata?.selfReviewRounds === 'number' ? item.metadata.selfReviewRounds : 0;\n const rounds = verdict === 'request changes' ? previousRounds + 1 : previousRounds;\n // Every SELF_REVIEW_ROUND_LIMIT rounds the card rests in Intake for a person instead of looping;\n // moving it back to Building grants another full set of rounds.\n const parked = verdict === 'request changes' && rounds % SELF_REVIEW_ROUND_LIMIT === 0;\n let updated: Awaited<ReturnType<WorkItemsStorage['update']>>;\n try {\n updated = await storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n selfReviewVerdict: verdict,\n selfReviewedHeadSha: reviewedHeadSha,\n selfReviewedAt: reviewedAt,\n selfReviewFindings: findings ?? null,\n selfReviewRounds: rounds,\n selfReviewNeedsHuman: parked,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error('The Work card changed while recording the verdict; call again to record it on the current card.');\n }\n if (!updated) throw new Error('Bound Factory work item not found.');\n if (verdict === 'approve')\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds };\n const transition = await input.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board: 'work',\n stage: parked ? 'intake' : 'execute',\n expectedRevision: updated.item.revision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${input.toolCallId}` },\n cause: parked\n ? `Self-review requested changes ${rounds} times; parked for a person to decide how to proceed.`\n : `Self-review requested changes on ${reviewedHeadSha} (round ${rounds}).`,\n });\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds, transition };\n}\n\n/**\n * A retired session's transition tool: it exists only to fail loudly. The run's\n * binding was revoked when its work item settled at a terminal stage, so there\n * is nothing to transition. Registering it (instead of dropping the tool) stops\n * a resumed model from inventing a false \"blocked\" state when the affordance it\n * expects has silently vanished.\n */\nfunction createRetiredSessionTool(workItemId: string, stage: string | undefined): IntegrationTools[string] {\n return createTool({\n id: 'factory_transition_work_item',\n description:\n 'This Factory session has been retired: its work item already reached a terminal stage and the run binding was revoked. No further transitions can be requested from this session.',\n inputSchema: transitionInputSchema,\n requireApproval: false,\n execute: async () => {\n throw new Error(\n `This Factory session has been retired: work item ${workItemId} reached a terminal stage${\n stage ? ` (${stage})` : ''\n } and its run binding was revoked. No transition can be requested from this session.`,\n );\n },\n });\n}\n"],"mappings":";;;;;;;;;;;AAkBA,MAAM,uBAAuB;AAE7B,MAAM,wBAAwB,EAC3B,OAAO;CACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAA,GAA+B,CAAC,CAAC,MAAM,mBAAmB;CAC5E,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAG5C,WAAW,EACR,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,CAAC,CAAC,CACN,WAAU,UACT,MAAM,UAAU,uBAAuB,QAAQ,GAAG,MAAM,MAAM,GAAG,GAAwB,EAAE,EAC7F;CAIF,YAAY,EAAE,KAAK,oBAAoB,CAAC,CAAC,SAAS;AACpD,CAAC,CAAC,CACD,OAAO;AAEV,MAAM,8BAA8B,sBAAsB,OAAO,EAC/D,YAAY,EAAE,KAAK,oBAAoB,EACzC,CAAC;AAED,eAAsB,6BAA6B,SAMrB;CAC5B,MAAM,aAAa,MAAM,6BAA6B;EACpD,gBAAgB,QAAQ;EACxB,SAAS,QAAQ;EACjB,UAAU,QAAQ;CACpB,CAAC;CACD,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,MAAM,mBAAmB,WAAW,WAAY,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO;CAC7G,IAAI,CAAC,kBAAkB;EAQrB,MAAM,UAAU,MAAM,QAAQ,QAAQ,wBAAwB,WAAW,OAAO;EAChF,IAAI,WAAW,QAAQ,WAAW,aAAa,QAAQ,QAAQ;GAC7D,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,QAAQ;GAAW,CAAC;GACvF,IACE,QACA,uBAAuB,MAAM,cAAc,QAAQ,MAAM,CAAC,CAAC,KAAK,OAAO,GAAG,IAAI,CAAC,EAAE,SAAS,YAE1F,OAAO,EAAE,8BAA8B,yBAAyB,QAAQ,YAAY,KAAK,OAAO,EAAE,EAAE;EAExG;EACA,OAAO,CAAC;CACV;CACA,IAAI,WAAW;CACf,IAAI,iBAAiB,SAAS,UAAU;EACtC,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;GAAE,OAAO,iBAAiB;GAAO,IAAI,iBAAiB;EAAW,CAAC;EACzG,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,WAAW,iBAAiB,IAAI,MAAM;CACxC;CAEA,OAAO;EACL,GAAI,iBAAiB,SAAS,WAAW,wBAAwB,SAAS,iBAAiB,UAAU,IAAI,CAAC;EAC1G,8BAA8B,WAAW;GACvC,IAAI;GACJ,aAAa,WACT,gXACA;GACJ,aAAa,WAAW,8BAA8B;GACtD,iBAAiB;GACjB,SAAS,OAAO,EAAE,OAAO,kBAAkB,WAAW,YAAY,uBAAuB,cAAc;IAMrG,MAAM,kBAAiB,MALS,6BAA6B;KAC3D,gBAAgB,UAAU;KAC1B,SAAS,QAAQ;KACjB,UAAU,QAAQ;IACpB,CAAC,EAAA,EACyC,WAAW;IACrD,MAAM,aAAa,UAAU,OAAO;IACpC,IAAI,CAAC,kBAAkB,CAAC,YACtB,MAAM,IAAI,MAAM,qEAAqE;IAEvF,MAAM,UAAU,MAAM,QAAQ,QAAQ,qBAAqB,cAAc;IAOzE,IAAI,CAAC,WAAW,QAAQ,eAAe,iBAAiB,YACtD,MAAM,IAAI,MAAM,mFAAmF;IAErG,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;KAAE,OAAO,QAAQ;KAAO,IAAI,QAAQ;IAAW,CAAC;IACvF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;IAC/D,MAAM,QAAQ,iBAAiB,IAAI;IAEnC,MAAM,aAAa,UAAU,UAAU,QAAQ,SAAS,WAAW,sBAAsB,KAAA;IAezF,OAAO,MAbc,QAAQ,kBAAkB,WAAW;KACxD,OAAO,QAAQ;KACf,kBAAkB,QAAQ;KAC1B,YAAY,QAAQ;KACpB;KACA;KACA;KACA,OAAO;MAAE,MAAM;MAAS,WAAW,QAAQ;MAAI,MAAM,QAAQ;KAAK;KAClE,SAAS;MAAE,MAAM;MAAS,UAAU,GAAG,QAAQ,GAAG,GAAG;KAAa;KAClE,OAAO;KACP,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;IACrC,CAAC;GAGH;EACF,CAAC;CACH;AACF;AAEA,MAAa,kBAAkB,CAAC,WAAW,iBAAiB;AAE5D,MAAM,kCAAkC;AAExC,MAAM,2BAA2B,EAAE,OAAO;CACxC,SAAS,EAAE,KAAK,eAAe;CAC/B,iBAAiB,EACd,OAAO,CAAC,CACR,MAAM,oCAAoC,+CAA+C;CAC5F,UAAU,EACP,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,CAAC,CAAC,CACN,WAAU,UACT,MAAM,UAAU,kCACZ,QACA,GAAG,MAAM,MAAM,GAAG,KAAmC,EAAE,EAC7D,CAAC,CACA,SAAS,CAAC,CACV,SACC,2JACF;AACJ,CAAC;;;;;;AAOD,SAAS,wBACP,SAMA,iBACkB;CAClB,OAAO,EACL,+BAA+B,WAAW;EACxC,IAAI;EACJ,aACE;EACF,aAAa;EACb,iBAAiB;EACjB,SAAS,OAAO,EAAE,SAAS,iBAAiB,YAAY,cAAc;GACpE,MAAM,aAAa,MAAM,6BAA6B;IACpD,gBAAgB,UAAU;IAC1B,SAAS,QAAQ;IACjB,UAAU,QAAQ;GACpB,CAAC;GACD,MAAM,UAAU,aAAa,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO,IAAI;GAC9F,IAAI,CAAC,WAAW,QAAQ,eAAe,mBAAmB,QAAQ,SAAS,UACzE,MAAM,IAAI,MAAM,oFAAoF;GAEtG,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,QAAQ;GAAW,CAAC;GACvF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;GAC/D,IACE,iBAAiB,IAAI,MAAM,UAC3B,KAAK,OAAO,OAAO,YACnB,OAAO,KAAK,UAAU,0BAA0B,UAEhD,OAAO,wBAAwB;IAC7B,SAAS,QAAQ;IACjB,mBAAmB,QAAQ;IAC3B;IACA;IACA;IACA,iBAAiB,gBAAgB,YAAY;IAC7C;IACA,YAAY,UAAU,OAAO;GAC/B,CAAC;GAEH,IAAI,iBAAiB,IAAI,MAAM,YAAY,KAAK,OAAO,OAAO,UAC5D,MAAM,IAAI,MAAM,8DAA8D,KAAK,OAAO,KAAK,IAAI,EAAE,EAAE;GAEzG,MAAM,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;GAG1C,IAAI;IACF,MAAM,QAAQ,QAAQ,OAAO;KAC3B,OAAO,QAAQ;KACf,IAAI,KAAK;KACT,QAAQ,SAAS,QAAQ;KACzB,kBAAkB,KAAK;KACvB,OAAO,EACL,UAAU;MACR,GAAI,KAAK,YAAY,CAAC;MACtB,eAAe;MACf,iBAAiB,gBAAgB,YAAY;MAC7C;KACF,EACF;IACF,CAAC;GACH,SAAS,OAAO;IACd,IAAI,EAAE,iBAAiB,8BAA8B,MAAM;IAC3D,MAAM,IAAI,MACR,mGACF;GACF;GAGA,MAAM,SAAS,KAAK,mBAChB,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,KAAK;GAAiB,CAAC,IAC7E;GACJ,IAAI,UAAU,iBAAiB,MAAM,MAAM,QACzC,MAAM,QAAQ,QAAQ,OAAO;IAC3B,OAAO,QAAQ;IACf,IAAI,OAAO;IACX,QAAQ,SAAS,QAAQ;IACzB,OAAO,EACL,UAAU;KAAE,eAAe;KAAS,iBAAiB,gBAAgB,YAAY;KAAG;IAAW,EACjG;GACF,CAAC;GAEH,OAAO;IAAE,QAAQ;IAAY;IAAS;IAAiB;GAAW;EACpE;CACF,CAAC,EACH;AACF;;;;;;AAOA,eAAe,wBAAwB,OASpC;CACD,MAAM,EAAE,SAAS,SAAS,MAAM,SAAS,iBAAiB,aAAa;CACvE,IAAI,CAAC,MAAM,YAAY,MAAM,IAAI,MAAM,sEAAsE;CAC7G,MAAM,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;CAC1C,MAAM,iBAAiB,OAAO,KAAK,UAAU,qBAAqB,WAAW,KAAK,SAAS,mBAAmB;CAC9G,MAAM,SAAS,YAAY,oBAAoB,iBAAiB,IAAI;CAGpE,MAAM,SAAS,YAAY,qBAAqB,SAAA,MAAqC;CACrF,IAAI;CACJ,IAAI;EACF,UAAU,MAAM,QAAQ,OAAO;GAC7B,OAAO,QAAQ;GACf,IAAI,KAAK;GACT,QAAQ,SAAS,QAAQ;GACzB,kBAAkB,KAAK;GACvB,OAAO,EACL,UAAU;IACR,mBAAmB;IACnB,qBAAqB;IACrB,gBAAgB;IAChB,oBAAoB,YAAY;IAChC,kBAAkB;IAClB,sBAAsB;GACxB,EACF;EACF,CAAC;CACH,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,8BAA8B,MAAM;EAC3D,MAAM,IAAI,MAAM,iGAAiG;CACnH;CACA,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,oCAAoC;CAClE,IAAI,YAAY,WACd,OAAO;EAAE,QAAQ;EAAY;EAAS;EAAiB;EAAY,kBAAkB;CAAO;CAc9F,OAAO;EAAE,QAAQ;EAAY;EAAS;EAAiB;EAAY,kBAAkB;EAAQ,YAAA,MAbpE,MAAM,kBAAkB,WAAW;GAC1D,OAAO,QAAQ;GACf,kBAAkB,QAAQ;GAC1B,YAAY,QAAQ;GACpB,OAAO;GACP,OAAO,SAAS,WAAW;GAC3B,kBAAkB,QAAQ,KAAK;GAC/B,OAAO;IAAE,MAAM;IAAS,WAAW,QAAQ;IAAI,MAAM,QAAQ;GAAK;GAClE,SAAS;IAAE,MAAM;IAAS,UAAU,GAAG,QAAQ,GAAG,GAAG,MAAM;GAAa;GACxE,OAAO,SACH,iCAAiC,OAAO,yDACxC,oCAAoC,gBAAgB,UAAU,OAAO;EAC3E,CAAC;CACuG;AAC1G;;;;;;;;AASA,SAAS,yBAAyB,YAAoB,OAAqD;CACzG,OAAO,WAAW;EAChB,IAAI;EACJ,aACE;EACF,aAAa;EACb,iBAAiB;EACjB,SAAS,YAAY;GACnB,MAAM,IAAI,MACR,oDAAoD,WAAW,2BAC7D,QAAQ,KAAK,MAAM,KAAK,GACzB,oFACH;EACF;CACF,CAAC;AACH"}
@@ -1,4 +1,5 @@
1
1
  import type { BoardRegistry } from '../boards/index.js';
2
+ import type { ProjectBoardSource } from '../boards/project-boards.js';
2
3
  import type { AuditActorProfileInput, AuditActorType, AuditContext } from '../storage/domains/audit/base.js';
3
4
  import type { AuditRecorder } from '../storage/domains/audit/domain.js';
4
5
  import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
@@ -34,7 +35,8 @@ export interface FactoryTransitionRequest {
34
35
  export interface FactoryTransitionServiceOptions {
35
36
  configVersion: string;
36
37
  storage: WorkItemsStorage;
37
- boards?: BoardRegistry;
38
+ /** A fixed registry, or the per-project boards; each transition loads its project's boards. */
39
+ boards?: BoardRegistry | ProjectBoardSource;
38
40
  /** Every commit, accepted or rejected, lands here as `stage_moved` / `transition_rejected` under the request's actor. */
39
41
  audit?: AuditRecorder;
40
42
  timeoutMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import { externallyAuthoredWorkItem, factoryRuleSourceForWorkItem, isFactoryRuleStage, workItemSource } from "./types.js";
2
2
  import { assertFactoryDecisionTarget, validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
3
- import { createBoardRegistry } from "../boards/registry.js";
4
- import "../boards/index.js";
3
+ import { toBoardSource } from "../boards/project-boards.js";
5
4
  import { isAgentActor } from "../storage/domains/work-items/base.js";
6
5
  import { autoApprovedExitTool } from "../boards/phase-automation.js";
7
6
  import { boardTransitionPolicyResultSchema, immutablePolicySnapshot } from "../boards/transition-policy.js";
@@ -117,7 +116,7 @@ var FactoryTransitionService = class {
117
116
  #audit;
118
117
  constructor(options) {
119
118
  this.#configVersion = options.configVersion;
120
- this.#boards = options.boards ?? createBoardRegistry();
119
+ this.#boards = toBoardSource(options.boards);
121
120
  this.#storage = options.storage;
122
121
  this.#audit = options.audit;
123
122
  this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;
@@ -206,16 +205,17 @@ var FactoryTransitionService = class {
206
205
  });
207
206
  }
208
207
  async #evaluateAndCommit(request, transitionId, item) {
208
+ const boards = await this.#boards.load(request);
209
209
  if (request.causalChain && request.causalChain.length > 8) return this.#commitRejection(request, transitionId, "causal_depth_exceeded", "Factory rule causal depth exceeded.");
210
210
  const itemSource = workItemSource(item.externalSource);
211
211
  const source = factoryRuleSourceForWorkItem(itemSource);
212
212
  const isPullRequest = source === "pullRequest" || source === "gitlabPullRequest";
213
213
  const legacyBoard = isPullRequest ? "review" : "work";
214
- if (item.board === null && !this.#boards.has(legacyBoard)) return this.#commitRejection(request, transitionId, "invalid_transition", "This legacy work item has no assigned board. Assign an installed board and phase through the work-item PATCH endpoint before transitioning it.");
214
+ if (item.board === null && !boards.has(legacyBoard)) return this.#commitRejection(request, transitionId, "invalid_transition", "This legacy work item has no assigned board. Assign an installed board and phase through the work-item PATCH endpoint before transitioning it.");
215
215
  const itemBoard = item.board ?? legacyBoard;
216
216
  if (request.board !== itemBoard) return this.#commitRejection(request, transitionId, "invalid_transition", `The work item belongs to board "${itemBoard}", not "${request.board}".`);
217
217
  if (itemBoard === "review" && !isPullRequest || itemBoard === "work" && isPullRequest) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not belong to the requested board.");
218
- const board = this.#boards.get(request.board);
218
+ const board = boards.get(request.board);
219
219
  if (!board) return this.#commitRejection(request, transitionId, "invalid_transition", `Board "${request.board}" is not installed.`);
220
220
  const fromStage = item.stages.length === 1 ? item.stages[0] : void 0;
221
221
  if (!fromStage || !Object.prototype.hasOwnProperty.call(board.phases, fromStage)) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not have one canonical phase on the requested board.");
@@ -260,7 +260,7 @@ var FactoryTransitionService = class {
260
260
  let evaluation;
261
261
  try {
262
262
  evaluation = await withRuleTimeout((async () => {
263
- const plansAutoApproved = autoApprovedExitTool(this.#boards, item) === "submit_plan" || (this.#autoApprovePlans ? await this.#autoApprovePlans({
263
+ const plansAutoApproved = autoApprovedExitTool(boards, item) === "submit_plan" || (this.#autoApprovePlans ? await this.#autoApprovePlans({
264
264
  orgId: request.orgId,
265
265
  factoryProjectId: request.factoryProjectId
266
266
  }) : false);
@@ -294,7 +294,7 @@ var FactoryTransitionService = class {
294
294
  factoryProjectId: request.factoryProjectId
295
295
  }) : false;
296
296
  const decisions = [];
297
- for (const rule of resolveFactoryStageRules(this.#boards, {
297
+ for (const rule of resolveFactoryStageRules(boards, {
298
298
  board: request.board,
299
299
  source,
300
300
  fromStage,
@@ -315,7 +315,7 @@ var FactoryTransitionService = class {
315
315
  code: decision.code,
316
316
  reason: decision.reason
317
317
  };
318
- assertFactoryDecisionTarget(decision, this.#boards, itemBoard);
318
+ assertFactoryDecisionTarget(decision, boards, itemBoard);
319
319
  if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;
320
320
  decisions.push(decision);
321
321
  }
@@ -357,16 +357,16 @@ var FactoryTransitionService = class {
357
357
  return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ? {
358
358
  ...consentEffect(request, entersWorking, humanMove),
359
359
  ...evaluation.intents
360
- } : {});
360
+ } : {}, boards);
361
361
  }
362
362
  async #commitRejection(request, transitionId, code, reason) {
363
363
  return this.#commit(request, transitionId, {
364
364
  outcome: "rejected",
365
365
  code,
366
366
  reason: reason.slice(0, MAX_REJECTION_REASON)
367
- });
367
+ }, {}, void 0);
368
368
  }
369
- async #commit(request, transitionId, evaluation, options = {}) {
369
+ async #commit(request, transitionId, evaluation, options, boards) {
370
370
  const committed = await this.#storage.commitTransition({
371
371
  autonomy: options.autonomy,
372
372
  consentedBy: options.consentedBy,
@@ -401,7 +401,7 @@ var FactoryTransitionService = class {
401
401
  console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);
402
402
  });
403
403
  }
404
- if (this.#onTerminalStage && result.status === "accepted" && this.#boards.get(request.board)?.isTerminal(result.stage) === true) {
404
+ if (this.#onTerminalStage && result.status === "accepted" && boards?.get(request.board)?.isTerminal(result.stage) === true) {
405
405
  let timer;
406
406
  try {
407
407
  const cleanup = Promise.resolve(this.#onTerminalStage({