@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":"transition-service.js","names":[],"sources":["../../src/rules/transition-service.ts"],"sourcesContent":["import { createBoardRegistry } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { autoApprovedExitTool } from '../boards/phase-automation.js';\nimport { boardTransitionPolicyResultSchema, immutablePolicySnapshot } from '../boards/transition-policy.js';\nimport type { AuditActorProfileInput, AuditActorType, AuditContext } from '../storage/domains/audit/base.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport { isAgentActor } from '../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryStageRules } from './resolve.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleActor,\n FactoryRuleBoard,\n FactoryRuleCausalEntry,\n FactoryRuleRejectionCode,\n FactoryRuleStage,\n FactoryTriageType,\n FactoryStageRuleContext,\n FactoryTransitionResult,\n} from './types.js';\nimport {\n externallyAuthoredWorkItem,\n factoryRuleSourceForWorkItem,\n isFactoryRuleStage,\n workItemSource,\n} from './types.js';\nimport {\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n assertFactoryDecisionTarget,\n validateFactoryRuleDecision,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\nconst MAX_REJECTION_REASON = 512;\n/** Longest a committed transition waits for terminal resource cleanup. Cleanup\n * reattaches remote sandboxes, so a hung provider call must not leave the\n * already-committed transition request pending; past this bound the cleanup\n * keeps running in the background as pure best-effort. */\nconst TERMINAL_CLEANUP_TIMEOUT_MS = 30_000;\n\nexport interface FactoryTransitionRequest {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n /** Installed board id; the service rejects boards that are not installed. */\n board: string;\n stage: FactoryRuleStage;\n expectedRevision: number;\n actor: FactoryRuleActor;\n actorProfile?: AuditActorProfileInput;\n /** Where a browser request came from; rules and agents carry none. */\n context?: AuditContext;\n ingress: { type: 'human' | 'agent' | 'toolResult' | 'github' | 'rule'; identity: string; transitionId?: string };\n cause: string;\n causalChain?: readonly FactoryRuleCausalEntry[];\n /** Internal materialization path: evaluate only the destination onEnter leaf even when already at that stage. */\n initialEntry?: boolean;\n /** Re-runs the stage's entry rules when the item already holds that stage, to restart work the entry invalidated. */\n reenter?: boolean;\n /** Structured verdict required from a bound triage-agent terminal request. */\n triageType?: FactoryTriageType;\n /** Internal proof that this move consumes an approved `submit_plan` result. */\n planApproved?: true;\n}\n\nexport interface FactoryTransitionServiceOptions {\n configVersion: string;\n storage: WorkItemsStorage;\n boards?: BoardRegistry;\n /** Every commit, accepted or rejected, lands here as `stage_moved` / `transition_rejected` under the request's actor. */\n audit?: AuditRecorder;\n timeoutMs?: number;\n /**\n * Called after a transition commits into a phase the board declares\n * terminal — the point where the item's sessions stop receiving runs, so\n * resources they hold (e.g. sandboxes) can be released for reuse. Awaited,\n * but failures are swallowed: releasing resources must never break or roll\n * back the committed transition.\n */\n onTerminalStage?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n stage: FactoryRuleStage;\n revision: number;\n /** The actor that committed this terminal transition. Lets cleanup leave\n * the seat that drove its own transition (an agent tool call) untouched. */\n actor: FactoryRuleActor;\n }) => Promise<void> | void;\n /**\n * Called after a transition commits a stage change, e.g. to tell the item's\n * followers. Not awaited, and failures are logged: a notification must never\n * fail or delay the committed transition.\n */\n onTransitionCommitted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n fromStage: string | undefined;\n toStage: string;\n }) => Promise<void> | void;\n /** Upper bound on how long a committed transition waits for\n * `onTerminalStage` before returning (default 30s). The cleanup continues\n * in the background past the bound. */\n terminalCleanupTimeoutMs?: number;\n /**\n * Called after a transition first records a person's acceptance of a\n * non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures\n * are swallowed, the committed transition never depends on it.\n */\n onAccepted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n item: WorkItemRow;\n }) => Promise<void> | void;\n /**\n * Resolves whether a project auto-approves produced plans. Unset means off:\n * a plan without explicit approval is a plan a person must review.\n */\n autoApprovePlans?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n /** Resolves whether a project self-reviews Work items before the change request opens. Unset means off. */\n selfReviewEnabled?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n}\n\nfunction rejection(\n transitionId: string,\n itemId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n): FactoryTransitionResult {\n return { status: 'rejected', transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };\n}\n\nfunction actorId(actor: FactoryRuleActor): string {\n switch (actor.type) {\n case 'human':\n case 'system':\n return actor.id;\n case 'agent':\n return `agent:${actor.bindingId}`;\n case 'github':\n return `github:${actor.login}`;\n case 'gitlab':\n return `gitlab:${actor.username}`;\n }\n}\n\n// The dispatcher executes an agent-approved decision as a human actor so its consent carries; the trail still names the agent.\nexport function auditActorOf(actor: FactoryRuleActor): { actorId: string; actorType: AuditActorType } {\n const id = actorId(actor);\n switch (actor.type) {\n case 'github':\n case 'gitlab':\n return { actorId: id, actorType: 'human' };\n case 'human':\n return { actorId: id, actorType: isAgentActor(id) ? 'agent' : 'human' };\n default:\n return { actorId: id, actorType: actor.type };\n }\n}\n\nexport function currentStage(stages: readonly string[]): FactoryRuleStage | undefined {\n if (stages.length !== 1) return undefined;\n const stage = stages[0];\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\ninterface TransitionConsentOptions {\n autonomy?: 'arm' | 'disarm';\n consentedBy?: string;\n accept?: boolean;\n triageType?: FactoryTriageType;\n}\n\n// Entering a resting lane disarms whoever rests it; only a person's move into a working lane arms.\nfunction transitionConsent(working: boolean, humanMove: boolean): 'arm' | 'disarm' | undefined {\n if (!working) return 'disarm';\n return humanMove ? 'arm' : undefined;\n}\n\n// An event arriving as data (GitHub, sweeps) never pre-approves the runs its transition queues.\nfunction bearsConsent(actor: FactoryRuleActor): boolean {\n return actor.type === 'human' || actor.type === 'agent';\n}\n\n// Rides the transition's own revision-checked commit, so a stale or rejected commit flips nothing.\nfunction consentEffect(\n request: FactoryTransitionRequest,\n working: boolean,\n humanMove: boolean,\n): TransitionConsentOptions {\n const autonomy = transitionConsent(working, humanMove);\n return bearsConsent(request.actor) ? { autonomy, consentedBy: actorId(request.actor) } : { autonomy };\n}\n\ntype RunStartDecision = Extract<FactoryCommitDecision, { type: 'invokeSkill' | 'sendMessage' }>;\n\nfunction startsRun(decision: FactoryCommitDecision): decision is RunStartDecision {\n return decision.type === 'invokeSkill' || (decision.type === 'sendMessage' && decision.prepareBinding === true);\n}\n\n// Answering a recorded run start, or the role's own mid-run agent, with a run would start a second one.\n// A run that starts after its own agent's move is queued onto that agent's run instead.\nfunction runAlreadyUnderway(request: FactoryTransitionRequest, decision: RunStartDecision): boolean {\n if (request.cause === 'run_start') return true;\n if (decision.type === 'invokeSkill' && decision.startsAfterOwnMove === true) return false;\n return request.actor.type === 'agent' && request.actor.role === decision.role;\n}\n\nfunction stageTransitionMessage(fromStage: FactoryRuleStage, toStage: FactoryRuleStage): string {\n return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;\n}\n\nfunction isTriageAgent(actor: FactoryRuleActor): actor is Extract<FactoryRuleActor, { type: 'agent' }> {\n return actor.type === 'agent' && actor.role === 'triage';\n}\n\nfunction isHumanTransition(request: FactoryTransitionRequest): boolean {\n return request.actor.type === 'human' && request.ingress.type === 'human';\n}\n\nfunction ruleFailure(error: unknown): { code: FactoryRuleRejectionCode; reason: string } {\n return {\n code: 'rule_error',\n reason: error instanceof Error ? `Factory rule failed: ${error.message}` : 'Factory rule failed.',\n };\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), timeoutMs);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryTransitionService {\n readonly #configVersion: string;\n readonly #boards: BoardRegistry;\n readonly #storage: WorkItemsStorage;\n readonly #timeoutMs: number;\n readonly #onTerminalStage: FactoryTransitionServiceOptions['onTerminalStage'];\n readonly #terminalCleanupTimeoutMs: number;\n readonly #onAccepted: FactoryTransitionServiceOptions['onAccepted'];\n readonly #onTransitionCommitted: FactoryTransitionServiceOptions['onTransitionCommitted'];\n readonly #autoApprovePlans: FactoryTransitionServiceOptions['autoApprovePlans'];\n readonly #selfReviewEnabled: FactoryTransitionServiceOptions['selfReviewEnabled'];\n readonly #audit: AuditRecorder | undefined;\n\n constructor(options: FactoryTransitionServiceOptions) {\n this.#configVersion = options.configVersion;\n this.#boards = options.boards ?? createBoardRegistry();\n this.#storage = options.storage;\n this.#audit = options.audit;\n this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;\n this.#onTerminalStage = options.onTerminalStage;\n this.#onAccepted = options.onAccepted;\n this.#onTransitionCommitted = options.onTransitionCommitted;\n this.#autoApprovePlans = options.autoApprovePlans;\n this.#selfReviewEnabled = options.selfReviewEnabled;\n this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;\n }\n\n get configVersion(): string {\n return this.#configVersion;\n }\n\n async transition(request: FactoryTransitionRequest): Promise<FactoryTransitionResult> {\n const replay = await this.#storage.getTransitionResultByIngress(\n request.orgId,\n request.factoryProjectId,\n request.ingress.identity,\n );\n if (replay) return replay as unknown as FactoryTransitionResult;\n\n const transitionId = request.ingress.transitionId ?? globalThis.crypto.randomUUID();\n const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });\n if (!item) {\n const rejection = await this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'Work item not found.',\n );\n await this.#recordTransition(request, undefined, rejection);\n return rejection;\n }\n const result = await this.#evaluateAndCommit(request, transitionId, item);\n await this.#recordTransition(request, item, result);\n this.#notifyCommitted(request, item, result);\n return result;\n }\n\n #notifyCommitted(request: FactoryTransitionRequest, item: WorkItemRow, result: FactoryTransitionResult): void {\n const onTransitionCommitted = this.#onTransitionCommitted;\n if (!onTransitionCommitted || result.status !== 'accepted') return;\n // Board-neutral: `currentStage` only knows the built-in rule stages.\n const fromStage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (result.stage === fromStage) return;\n // Inside the chain so a synchronous throw is isolated the same way an async rejection is.\n void Promise.resolve()\n .then(() =>\n onTransitionCommitted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: item.id,\n fromStage,\n toStage: result.stage,\n }),\n )\n .catch(error => {\n console.warn(`[factory] stage notification failed for work item ${item.id}:`, error);\n });\n }\n\n /** A rejection can outlive its work item: the row still names the id the caller asked for. */\n async #recordTransition(\n request: FactoryTransitionRequest,\n item: WorkItemRow | undefined,\n result: FactoryTransitionResult,\n ): Promise<void> {\n if (!this.#audit) return;\n const from = item ? currentStage(item.stages) : undefined;\n if (result.status === 'accepted' && result.stage === from && !request.reenter) return;\n const outcome =\n result.status === 'accepted'\n ? { action: 'factory.work_item.stage_moved' as const, to: result.stage, revision: result.revision }\n : {\n action: 'factory.work_item.transition_rejected' as const,\n to: request.stage,\n code: result.code,\n reason: result.reason,\n };\n const { action, ...detail } = outcome;\n await this.#audit\n .record({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n ...auditActorOf(request.actor),\n actorProfile: request.actorProfile,\n ...(request.context ? { context: request.context } : {}),\n action,\n idempotencyKey: result.transitionId,\n targets: [{ type: 'work_item', id: item?.id ?? request.workItemId, ...(item ? { name: item.title } : {}) }],\n metadata: {\n transitionId: result.transitionId,\n ingressType: request.ingress.type,\n cause: request.cause,\n configVersion: this.#configVersion,\n ...(from ? { from } : {}),\n ...(request.reenter ? { reenter: true } : {}),\n ...detail,\n },\n })\n .catch(error => {\n console.warn(`[factory] audit failed for transition ${result.transitionId}:`, error);\n });\n }\n\n async #evaluateAndCommit(\n request: FactoryTransitionRequest,\n transitionId: string,\n item: WorkItemRow,\n ): Promise<FactoryTransitionResult> {\n if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n return this.#commitRejection(\n request,\n transitionId,\n 'causal_depth_exceeded',\n 'Factory rule causal depth exceeded.',\n );\n }\n const itemSource = workItemSource(item.externalSource);\n const source = factoryRuleSourceForWorkItem(itemSource);\n const isPullRequest = source === 'pullRequest' || source === 'gitlabPullRequest';\n const legacyBoard = isPullRequest ? 'review' : 'work';\n if (item.board === null && !this.#boards.has(legacyBoard)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'This legacy work item has no assigned board. Assign an installed board and phase through the work-item PATCH endpoint before transitioning it.',\n );\n }\n const itemBoard = item.board ?? legacyBoard;\n if (request.board !== itemBoard) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The work item belongs to board \"${itemBoard}\", not \"${request.board}\".`,\n );\n }\n if ((itemBoard === 'review' && !isPullRequest) || (itemBoard === 'work' && isPullRequest)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not belong to the requested board.',\n );\n }\n const board = this.#boards.get(request.board);\n if (!board) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `Board \"${request.board}\" is not installed.`,\n );\n }\n const fromStage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!fromStage || !Object.prototype.hasOwnProperty.call(board.phases, fromStage)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not have one canonical phase on the requested board.',\n );\n }\n if (\n !Object.prototype.hasOwnProperty.call(board.phases, request.stage) ||\n !board.allowsTransition(fromStage, request.stage)\n ) {\n const nextStages = [...new Set((board.transitions[fromStage] ?? []).map(transition => transition.to))];\n const nextStagesReason =\n nextStages.length > 0\n ? `Next stages declared from ${fromStage}: ${nextStages.join(', ')}.`\n : `No next stage is declared from ${fromStage}.`;\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The ${board.name} board does not allow moving from ${fromStage} to ${request.stage}. ${nextStagesReason}`,\n );\n }\n\n // The coordinator's own self-move at run start would otherwise inject a second run's kickoff.\n const humanMove = request.actor.type === 'human' && fromStage !== request.stage && request.cause !== 'run_start';\n // The board, not the phase name, says whether a seat is engaged on either side of this move.\n const entersWorking = board.isWorking(request.stage);\n const seatRole = board.roleForPhase(request.stage);\n\n const contextBase = {\n tenant: { orgId: request.orgId, projectId: request.factoryProjectId },\n actor: request.actor,\n ingress: { type: request.ingress.type, id: request.ingress.identity },\n cause: request.cause,\n causalChain: request.causalChain ?? [],\n configVersion: this.#configVersion,\n item: {\n id: item.id,\n source: itemSource,\n sourceKey: item.externalSource\n ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}`\n : null,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: [...item.stages],\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board: request.board,\n itemRevision: item.revision,\n source,\n fromStage,\n toStage: request.stage,\n } satisfies Omit<FactoryStageRuleContext, 'stage'>;\n\n let evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[]; intents: TransitionConsentOptions }\n | { outcome: 'rejected'; code: string; reason: string };\n try {\n evaluation = await withRuleTimeout(\n (async () => {\n // Resolve the project switch inside the timed block so a resolver rejection\n // surfaces as a committed rule_error and a slow lookup is bounded.\n // A phase whose submit_plan exit tool is auto-approved for this card approves its plans too.\n const plansAutoApproved =\n autoApprovedExitTool(this.#boards, item) === 'submit_plan' ||\n (this.#autoApprovePlans\n ? await this.#autoApprovePlans({ orgId: request.orgId, factoryProjectId: request.factoryProjectId })\n : false);\n const policy = boardTransitionPolicyResultSchema.parse(\n await board.transitionPolicy?.(\n immutablePolicySnapshot({\n ...contextBase,\n item: { ...contextBase.item, triageType: item.triageType },\n initialEntry: request.initialEntry ?? false,\n reenter: request.reenter ?? false,\n isHumanTransition: isHumanTransition(request),\n plansAutoApproved,\n planApproved: request.planApproved === true,\n requestedTriageType: request.triageType,\n }),\n ),\n );\n if (policy?.type === 'reject') {\n return { outcome: 'rejected' as const, code: policy.code, reason: policy.reason };\n }\n if (\n policy?.triageType !== undefined &&\n (!isTriageAgent(request.actor) ||\n policy.triageType !== request.triageType ||\n (item.triageType !== null && item.triageType !== policy.triageType))\n ) {\n throw new Error('Board policy requested an unauthorized classification.');\n }\n if (policy?.accept && !isHumanTransition(request)) {\n throw new Error('Board policy requested unauthorized acceptance.');\n }\n // External content can steer a bound agent; board allowance cannot bypass this guard.\n if (\n request.actor.type === 'agent' &&\n !board.isWorking(fromStage) &&\n entersWorking &&\n externallyAuthoredWorkItem(item)\n ) {\n return {\n outcome: 'rejected' as const,\n code: 'approval_required',\n reason:\n 'This card comes from outside the write-access circle; a person must resume it from the Factory board.',\n };\n }\n const selfReviewEnabled =\n request.board === 'work' && this.#selfReviewEnabled\n ? await this.#selfReviewEnabled({ orgId: request.orgId, factoryProjectId: request.factoryProjectId })\n : false;\n const decisions: FactoryCommitDecision[] = [];\n for (const rule of resolveFactoryStageRules(this.#boards, {\n board: request.board,\n source,\n fromStage,\n toStage: request.stage,\n initialEntry: request.initialEntry,\n reenter: request.reenter,\n })) {\n const context: FactoryStageRuleContext = Object.freeze({\n ...contextBase,\n selfReviewEnabled,\n stage: rule.phase === 'exit' ? fromStage : request.stage,\n });\n const raw = await rule.handler(context);\n if (raw === undefined) continue;\n const decision = validateFactoryRuleDecision(raw, context.causalChain.length);\n if (decision.type === 'reject') {\n return { outcome: 'rejected' as const, code: decision.code, reason: decision.reason };\n }\n assertFactoryDecisionTarget(decision, this.#boards, itemBoard);\n if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;\n decisions.push(decision);\n }\n const validated = validateFactoryRuleDecisions(decisions);\n if (humanMove) {\n const message = stageTransitionMessage(fromStage, request.stage);\n const skill = validated.find(decision => decision.type === 'invokeSkill');\n if (skill) {\n skill.precedingMessage = message;\n } else {\n validated.unshift({\n type: 'sendMessage',\n idempotencyKey: `factory-stage:${transitionId}`,\n message,\n priority: 'urgent',\n idleBehavior: 'wake',\n // Parking a card says stop: no seat is right by construction, so\n // the notice goes to whichever session is live — or nobody.\n ...(entersWorking && seatRole !== undefined ? { role: seatRole, prepareBinding: true } : {}),\n });\n }\n }\n return {\n outcome: 'accepted' as const,\n intents: { triageType: policy?.triageType, accept: policy?.accept === true && !item.acceptedAt },\n decisions: validateFactoryRuleDecisions(validated) as unknown as Record<string, unknown>[],\n };\n })(),\n this.#timeoutMs,\n );\n } catch (error) {\n const failed =\n error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT'\n ? { code: 'timeout' as const, reason: 'Factory rule evaluation timed out.' }\n : ruleFailure(error);\n evaluation = { outcome: 'rejected', ...failed };\n }\n return this.#commit(\n request,\n transitionId,\n evaluation,\n evaluation.outcome === 'accepted'\n ? { ...consentEffect(request, entersWorking, humanMove), ...evaluation.intents }\n : {},\n );\n }\n\n async #commitRejection(\n request: FactoryTransitionRequest,\n transitionId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n ): Promise<FactoryTransitionResult> {\n return this.#commit(request, transitionId, {\n outcome: 'rejected',\n code,\n reason: reason.slice(0, MAX_REJECTION_REASON),\n });\n }\n\n async #commit(\n request: FactoryTransitionRequest,\n transitionId: string,\n evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string },\n options: TransitionConsentOptions = {},\n ): Promise<FactoryTransitionResult> {\n const committed = await this.#storage.commitTransition({\n autonomy: options.autonomy,\n consentedBy: options.consentedBy,\n ...(options.accept ? { accept: true } : {}),\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n expectedRevision: request.expectedRevision,\n destinationStage: request.stage,\n actorId: actorId(request.actor),\n ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },\n configVersion: this.#configVersion,\n causalChain: [...(request.causalChain ?? [])],\n evaluation,\n ...(options.triageType ? { triageType: options.triageType } : {}),\n });\n if (committed.status === 'missing') {\n return rejection(transitionId, request.workItemId, 'invalid_transition', 'Work item not found.');\n }\n const result = committed.result as unknown as FactoryTransitionResult;\n if (\n this.#onAccepted &&\n options.accept &&\n committed.status === 'committed' &&\n result.status === 'accepted' &&\n committed.item?.acceptedAt\n ) {\n const item = committed.item;\n const onAccepted = this.#onAccepted;\n // Invoke inside the chain so a synchronous throw is isolated the same way an async rejection is.\n void Promise.resolve()\n .then(() =>\n onAccepted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n item,\n }),\n )\n .catch(error => {\n console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);\n });\n }\n // Only an installed board's declaration releases resources; an unknown board or phase never does.\n if (\n this.#onTerminalStage &&\n result.status === 'accepted' &&\n this.#boards.get(request.board)?.isTerminal(result.stage) === true\n ) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const cleanup = Promise.resolve(\n this.#onTerminalStage({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n stage: result.stage,\n revision: result.revision,\n actor: request.actor,\n }),\n );\n // A late rejection after the timeout wins the race must not surface\n // as an unhandled rejection.\n cleanup.catch(() => {});\n await Promise.race([\n cleanup,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, this.#terminalCleanupTimeoutMs);\n }),\n ]);\n } catch {\n // Resource release is best-effort — never fail a committed transition.\n } finally {\n clearTimeout(timer);\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transition-service.js","names":[],"sources":["../../src/rules/transition-service.ts"],"sourcesContent":["import type { BoardRegistry } from '../boards/index.js';\nimport { autoApprovedExitTool } from '../boards/phase-automation.js';\nimport { toBoardSource } from '../boards/project-boards.js';\nimport type { ProjectBoardSource } from '../boards/project-boards.js';\nimport { boardTransitionPolicyResultSchema, immutablePolicySnapshot } from '../boards/transition-policy.js';\nimport type { AuditActorProfileInput, AuditActorType, AuditContext } from '../storage/domains/audit/base.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport { isAgentActor } from '../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryStageRules } from './resolve.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleActor,\n FactoryRuleBoard,\n FactoryRuleCausalEntry,\n FactoryRuleRejectionCode,\n FactoryRuleStage,\n FactoryTriageType,\n FactoryStageRuleContext,\n FactoryTransitionResult,\n} from './types.js';\nimport {\n externallyAuthoredWorkItem,\n factoryRuleSourceForWorkItem,\n isFactoryRuleStage,\n workItemSource,\n} from './types.js';\nimport {\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n assertFactoryDecisionTarget,\n validateFactoryRuleDecision,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\nconst MAX_REJECTION_REASON = 512;\n/** Longest a committed transition waits for terminal resource cleanup. Cleanup\n * reattaches remote sandboxes, so a hung provider call must not leave the\n * already-committed transition request pending; past this bound the cleanup\n * keeps running in the background as pure best-effort. */\nconst TERMINAL_CLEANUP_TIMEOUT_MS = 30_000;\n\nexport interface FactoryTransitionRequest {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n /** Installed board id; the service rejects boards that are not installed. */\n board: string;\n stage: FactoryRuleStage;\n expectedRevision: number;\n actor: FactoryRuleActor;\n actorProfile?: AuditActorProfileInput;\n /** Where a browser request came from; rules and agents carry none. */\n context?: AuditContext;\n ingress: { type: 'human' | 'agent' | 'toolResult' | 'github' | 'rule'; identity: string; transitionId?: string };\n cause: string;\n causalChain?: readonly FactoryRuleCausalEntry[];\n /** Internal materialization path: evaluate only the destination onEnter leaf even when already at that stage. */\n initialEntry?: boolean;\n /** Re-runs the stage's entry rules when the item already holds that stage, to restart work the entry invalidated. */\n reenter?: boolean;\n /** Structured verdict required from a bound triage-agent terminal request. */\n triageType?: FactoryTriageType;\n /** Internal proof that this move consumes an approved `submit_plan` result. */\n planApproved?: true;\n}\n\nexport interface FactoryTransitionServiceOptions {\n configVersion: string;\n storage: WorkItemsStorage;\n /** A fixed registry, or the per-project boards; each transition loads its project's boards. */\n boards?: BoardRegistry | ProjectBoardSource;\n /** Every commit, accepted or rejected, lands here as `stage_moved` / `transition_rejected` under the request's actor. */\n audit?: AuditRecorder;\n timeoutMs?: number;\n /**\n * Called after a transition commits into a phase the board declares\n * terminal — the point where the item's sessions stop receiving runs, so\n * resources they hold (e.g. sandboxes) can be released for reuse. Awaited,\n * but failures are swallowed: releasing resources must never break or roll\n * back the committed transition.\n */\n onTerminalStage?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n stage: FactoryRuleStage;\n revision: number;\n /** The actor that committed this terminal transition. Lets cleanup leave\n * the seat that drove its own transition (an agent tool call) untouched. */\n actor: FactoryRuleActor;\n }) => Promise<void> | void;\n /**\n * Called after a transition commits a stage change, e.g. to tell the item's\n * followers. Not awaited, and failures are logged: a notification must never\n * fail or delay the committed transition.\n */\n onTransitionCommitted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n fromStage: string | undefined;\n toStage: string;\n }) => Promise<void> | void;\n /** Upper bound on how long a committed transition waits for\n * `onTerminalStage` before returning (default 30s). The cleanup continues\n * in the background past the bound. */\n terminalCleanupTimeoutMs?: number;\n /**\n * Called after a transition first records a person's acceptance of a\n * non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures\n * are swallowed, the committed transition never depends on it.\n */\n onAccepted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n item: WorkItemRow;\n }) => Promise<void> | void;\n /**\n * Resolves whether a project auto-approves produced plans. Unset means off:\n * a plan without explicit approval is a plan a person must review.\n */\n autoApprovePlans?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n /** Resolves whether a project self-reviews Work items before the change request opens. Unset means off. */\n selfReviewEnabled?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n}\n\nfunction rejection(\n transitionId: string,\n itemId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n): FactoryTransitionResult {\n return { status: 'rejected', transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };\n}\n\nfunction actorId(actor: FactoryRuleActor): string {\n switch (actor.type) {\n case 'human':\n case 'system':\n return actor.id;\n case 'agent':\n return `agent:${actor.bindingId}`;\n case 'github':\n return `github:${actor.login}`;\n case 'gitlab':\n return `gitlab:${actor.username}`;\n }\n}\n\n// The dispatcher executes an agent-approved decision as a human actor so its consent carries; the trail still names the agent.\nexport function auditActorOf(actor: FactoryRuleActor): { actorId: string; actorType: AuditActorType } {\n const id = actorId(actor);\n switch (actor.type) {\n case 'github':\n case 'gitlab':\n return { actorId: id, actorType: 'human' };\n case 'human':\n return { actorId: id, actorType: isAgentActor(id) ? 'agent' : 'human' };\n default:\n return { actorId: id, actorType: actor.type };\n }\n}\n\nexport function currentStage(stages: readonly string[]): FactoryRuleStage | undefined {\n if (stages.length !== 1) return undefined;\n const stage = stages[0];\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\ninterface TransitionConsentOptions {\n autonomy?: 'arm' | 'disarm';\n consentedBy?: string;\n accept?: boolean;\n triageType?: FactoryTriageType;\n}\n\n// Entering a resting lane disarms whoever rests it; only a person's move into a working lane arms.\nfunction transitionConsent(working: boolean, humanMove: boolean): 'arm' | 'disarm' | undefined {\n if (!working) return 'disarm';\n return humanMove ? 'arm' : undefined;\n}\n\n// An event arriving as data (GitHub, sweeps) never pre-approves the runs its transition queues.\nfunction bearsConsent(actor: FactoryRuleActor): boolean {\n return actor.type === 'human' || actor.type === 'agent';\n}\n\n// Rides the transition's own revision-checked commit, so a stale or rejected commit flips nothing.\nfunction consentEffect(\n request: FactoryTransitionRequest,\n working: boolean,\n humanMove: boolean,\n): TransitionConsentOptions {\n const autonomy = transitionConsent(working, humanMove);\n return bearsConsent(request.actor) ? { autonomy, consentedBy: actorId(request.actor) } : { autonomy };\n}\n\ntype RunStartDecision = Extract<FactoryCommitDecision, { type: 'invokeSkill' | 'sendMessage' }>;\n\nfunction startsRun(decision: FactoryCommitDecision): decision is RunStartDecision {\n return decision.type === 'invokeSkill' || (decision.type === 'sendMessage' && decision.prepareBinding === true);\n}\n\n// Answering a recorded run start, or the role's own mid-run agent, with a run would start a second one.\n// A run that starts after its own agent's move is queued onto that agent's run instead.\nfunction runAlreadyUnderway(request: FactoryTransitionRequest, decision: RunStartDecision): boolean {\n if (request.cause === 'run_start') return true;\n if (decision.type === 'invokeSkill' && decision.startsAfterOwnMove === true) return false;\n return request.actor.type === 'agent' && request.actor.role === decision.role;\n}\n\nfunction stageTransitionMessage(fromStage: FactoryRuleStage, toStage: FactoryRuleStage): string {\n return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;\n}\n\nfunction isTriageAgent(actor: FactoryRuleActor): actor is Extract<FactoryRuleActor, { type: 'agent' }> {\n return actor.type === 'agent' && actor.role === 'triage';\n}\n\nfunction isHumanTransition(request: FactoryTransitionRequest): boolean {\n return request.actor.type === 'human' && request.ingress.type === 'human';\n}\n\nfunction ruleFailure(error: unknown): { code: FactoryRuleRejectionCode; reason: string } {\n return {\n code: 'rule_error',\n reason: error instanceof Error ? `Factory rule failed: ${error.message}` : 'Factory rule failed.',\n };\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), timeoutMs);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryTransitionService {\n readonly #configVersion: string;\n readonly #boards: ProjectBoardSource;\n readonly #storage: WorkItemsStorage;\n readonly #timeoutMs: number;\n readonly #onTerminalStage: FactoryTransitionServiceOptions['onTerminalStage'];\n readonly #terminalCleanupTimeoutMs: number;\n readonly #onAccepted: FactoryTransitionServiceOptions['onAccepted'];\n readonly #onTransitionCommitted: FactoryTransitionServiceOptions['onTransitionCommitted'];\n readonly #autoApprovePlans: FactoryTransitionServiceOptions['autoApprovePlans'];\n readonly #selfReviewEnabled: FactoryTransitionServiceOptions['selfReviewEnabled'];\n readonly #audit: AuditRecorder | undefined;\n\n constructor(options: FactoryTransitionServiceOptions) {\n this.#configVersion = options.configVersion;\n this.#boards = toBoardSource(options.boards);\n this.#storage = options.storage;\n this.#audit = options.audit;\n this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;\n this.#onTerminalStage = options.onTerminalStage;\n this.#onAccepted = options.onAccepted;\n this.#onTransitionCommitted = options.onTransitionCommitted;\n this.#autoApprovePlans = options.autoApprovePlans;\n this.#selfReviewEnabled = options.selfReviewEnabled;\n this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;\n }\n\n get configVersion(): string {\n return this.#configVersion;\n }\n\n async transition(request: FactoryTransitionRequest): Promise<FactoryTransitionResult> {\n const replay = await this.#storage.getTransitionResultByIngress(\n request.orgId,\n request.factoryProjectId,\n request.ingress.identity,\n );\n if (replay) return replay as unknown as FactoryTransitionResult;\n\n const transitionId = request.ingress.transitionId ?? globalThis.crypto.randomUUID();\n const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });\n if (!item) {\n const rejection = await this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'Work item not found.',\n );\n await this.#recordTransition(request, undefined, rejection);\n return rejection;\n }\n const result = await this.#evaluateAndCommit(request, transitionId, item);\n await this.#recordTransition(request, item, result);\n this.#notifyCommitted(request, item, result);\n return result;\n }\n\n #notifyCommitted(request: FactoryTransitionRequest, item: WorkItemRow, result: FactoryTransitionResult): void {\n const onTransitionCommitted = this.#onTransitionCommitted;\n if (!onTransitionCommitted || result.status !== 'accepted') return;\n // Board-neutral: `currentStage` only knows the built-in rule stages.\n const fromStage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (result.stage === fromStage) return;\n // Inside the chain so a synchronous throw is isolated the same way an async rejection is.\n void Promise.resolve()\n .then(() =>\n onTransitionCommitted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: item.id,\n fromStage,\n toStage: result.stage,\n }),\n )\n .catch(error => {\n console.warn(`[factory] stage notification failed for work item ${item.id}:`, error);\n });\n }\n\n /** A rejection can outlive its work item: the row still names the id the caller asked for. */\n async #recordTransition(\n request: FactoryTransitionRequest,\n item: WorkItemRow | undefined,\n result: FactoryTransitionResult,\n ): Promise<void> {\n if (!this.#audit) return;\n const from = item ? currentStage(item.stages) : undefined;\n if (result.status === 'accepted' && result.stage === from && !request.reenter) return;\n const outcome =\n result.status === 'accepted'\n ? { action: 'factory.work_item.stage_moved' as const, to: result.stage, revision: result.revision }\n : {\n action: 'factory.work_item.transition_rejected' as const,\n to: request.stage,\n code: result.code,\n reason: result.reason,\n };\n const { action, ...detail } = outcome;\n await this.#audit\n .record({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n ...auditActorOf(request.actor),\n actorProfile: request.actorProfile,\n ...(request.context ? { context: request.context } : {}),\n action,\n idempotencyKey: result.transitionId,\n targets: [{ type: 'work_item', id: item?.id ?? request.workItemId, ...(item ? { name: item.title } : {}) }],\n metadata: {\n transitionId: result.transitionId,\n ingressType: request.ingress.type,\n cause: request.cause,\n configVersion: this.#configVersion,\n ...(from ? { from } : {}),\n ...(request.reenter ? { reenter: true } : {}),\n ...detail,\n },\n })\n .catch(error => {\n console.warn(`[factory] audit failed for transition ${result.transitionId}:`, error);\n });\n }\n\n async #evaluateAndCommit(\n request: FactoryTransitionRequest,\n transitionId: string,\n item: WorkItemRow,\n ): Promise<FactoryTransitionResult> {\n const boards = await this.#boards.load(request);\n if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n return this.#commitRejection(\n request,\n transitionId,\n 'causal_depth_exceeded',\n 'Factory rule causal depth exceeded.',\n );\n }\n const itemSource = workItemSource(item.externalSource);\n const source = factoryRuleSourceForWorkItem(itemSource);\n const isPullRequest = source === 'pullRequest' || source === 'gitlabPullRequest';\n const legacyBoard = isPullRequest ? 'review' : 'work';\n if (item.board === null && !boards.has(legacyBoard)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'This legacy work item has no assigned board. Assign an installed board and phase through the work-item PATCH endpoint before transitioning it.',\n );\n }\n const itemBoard = item.board ?? legacyBoard;\n if (request.board !== itemBoard) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The work item belongs to board \"${itemBoard}\", not \"${request.board}\".`,\n );\n }\n if ((itemBoard === 'review' && !isPullRequest) || (itemBoard === 'work' && isPullRequest)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not belong to the requested board.',\n );\n }\n const board = boards.get(request.board);\n if (!board) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `Board \"${request.board}\" is not installed.`,\n );\n }\n const fromStage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!fromStage || !Object.prototype.hasOwnProperty.call(board.phases, fromStage)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not have one canonical phase on the requested board.',\n );\n }\n if (\n !Object.prototype.hasOwnProperty.call(board.phases, request.stage) ||\n !board.allowsTransition(fromStage, request.stage)\n ) {\n const nextStages = [...new Set((board.transitions[fromStage] ?? []).map(transition => transition.to))];\n const nextStagesReason =\n nextStages.length > 0\n ? `Next stages declared from ${fromStage}: ${nextStages.join(', ')}.`\n : `No next stage is declared from ${fromStage}.`;\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The ${board.name} board does not allow moving from ${fromStage} to ${request.stage}. ${nextStagesReason}`,\n );\n }\n\n // The coordinator's own self-move at run start would otherwise inject a second run's kickoff.\n const humanMove = request.actor.type === 'human' && fromStage !== request.stage && request.cause !== 'run_start';\n // The board, not the phase name, says whether a seat is engaged on either side of this move.\n const entersWorking = board.isWorking(request.stage);\n const seatRole = board.roleForPhase(request.stage);\n\n const contextBase = {\n tenant: { orgId: request.orgId, projectId: request.factoryProjectId },\n actor: request.actor,\n ingress: { type: request.ingress.type, id: request.ingress.identity },\n cause: request.cause,\n causalChain: request.causalChain ?? [],\n configVersion: this.#configVersion,\n item: {\n id: item.id,\n source: itemSource,\n sourceKey: item.externalSource\n ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}`\n : null,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: [...item.stages],\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board: request.board,\n itemRevision: item.revision,\n source,\n fromStage,\n toStage: request.stage,\n } satisfies Omit<FactoryStageRuleContext, 'stage'>;\n\n let evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[]; intents: TransitionConsentOptions }\n | { outcome: 'rejected'; code: string; reason: string };\n try {\n evaluation = await withRuleTimeout(\n (async () => {\n // Resolve the project switch inside the timed block so a resolver rejection\n // surfaces as a committed rule_error and a slow lookup is bounded.\n // A phase whose submit_plan exit tool is auto-approved for this card approves its plans too.\n const plansAutoApproved =\n autoApprovedExitTool(boards, item) === 'submit_plan' ||\n (this.#autoApprovePlans\n ? await this.#autoApprovePlans({ orgId: request.orgId, factoryProjectId: request.factoryProjectId })\n : false);\n const policy = boardTransitionPolicyResultSchema.parse(\n await board.transitionPolicy?.(\n immutablePolicySnapshot({\n ...contextBase,\n item: { ...contextBase.item, triageType: item.triageType },\n initialEntry: request.initialEntry ?? false,\n reenter: request.reenter ?? false,\n isHumanTransition: isHumanTransition(request),\n plansAutoApproved,\n planApproved: request.planApproved === true,\n requestedTriageType: request.triageType,\n }),\n ),\n );\n if (policy?.type === 'reject') {\n return { outcome: 'rejected' as const, code: policy.code, reason: policy.reason };\n }\n if (\n policy?.triageType !== undefined &&\n (!isTriageAgent(request.actor) ||\n policy.triageType !== request.triageType ||\n (item.triageType !== null && item.triageType !== policy.triageType))\n ) {\n throw new Error('Board policy requested an unauthorized classification.');\n }\n if (policy?.accept && !isHumanTransition(request)) {\n throw new Error('Board policy requested unauthorized acceptance.');\n }\n // External content can steer a bound agent; board allowance cannot bypass this guard.\n if (\n request.actor.type === 'agent' &&\n !board.isWorking(fromStage) &&\n entersWorking &&\n externallyAuthoredWorkItem(item)\n ) {\n return {\n outcome: 'rejected' as const,\n code: 'approval_required',\n reason:\n 'This card comes from outside the write-access circle; a person must resume it from the Factory board.',\n };\n }\n const selfReviewEnabled =\n request.board === 'work' && this.#selfReviewEnabled\n ? await this.#selfReviewEnabled({ orgId: request.orgId, factoryProjectId: request.factoryProjectId })\n : false;\n const decisions: FactoryCommitDecision[] = [];\n for (const rule of resolveFactoryStageRules(boards, {\n board: request.board,\n source,\n fromStage,\n toStage: request.stage,\n initialEntry: request.initialEntry,\n reenter: request.reenter,\n })) {\n const context: FactoryStageRuleContext = Object.freeze({\n ...contextBase,\n selfReviewEnabled,\n stage: rule.phase === 'exit' ? fromStage : request.stage,\n });\n const raw = await rule.handler(context);\n if (raw === undefined) continue;\n const decision = validateFactoryRuleDecision(raw, context.causalChain.length);\n if (decision.type === 'reject') {\n return { outcome: 'rejected' as const, code: decision.code, reason: decision.reason };\n }\n assertFactoryDecisionTarget(decision, boards, itemBoard);\n if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;\n decisions.push(decision);\n }\n const validated = validateFactoryRuleDecisions(decisions);\n if (humanMove) {\n const message = stageTransitionMessage(fromStage, request.stage);\n const skill = validated.find(decision => decision.type === 'invokeSkill');\n if (skill) {\n skill.precedingMessage = message;\n } else {\n validated.unshift({\n type: 'sendMessage',\n idempotencyKey: `factory-stage:${transitionId}`,\n message,\n priority: 'urgent',\n idleBehavior: 'wake',\n // Parking a card says stop: no seat is right by construction, so\n // the notice goes to whichever session is live — or nobody.\n ...(entersWorking && seatRole !== undefined ? { role: seatRole, prepareBinding: true } : {}),\n });\n }\n }\n return {\n outcome: 'accepted' as const,\n intents: { triageType: policy?.triageType, accept: policy?.accept === true && !item.acceptedAt },\n decisions: validateFactoryRuleDecisions(validated) as unknown as Record<string, unknown>[],\n };\n })(),\n this.#timeoutMs,\n );\n } catch (error) {\n const failed =\n error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT'\n ? { code: 'timeout' as const, reason: 'Factory rule evaluation timed out.' }\n : ruleFailure(error);\n evaluation = { outcome: 'rejected', ...failed };\n }\n return this.#commit(\n request,\n transitionId,\n evaluation,\n evaluation.outcome === 'accepted'\n ? { ...consentEffect(request, entersWorking, humanMove), ...evaluation.intents }\n : {},\n boards,\n );\n }\n\n async #commitRejection(\n request: FactoryTransitionRequest,\n transitionId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n ): Promise<FactoryTransitionResult> {\n return this.#commit(\n request,\n transitionId,\n { outcome: 'rejected', code, reason: reason.slice(0, MAX_REJECTION_REASON) },\n {},\n undefined,\n );\n }\n\n async #commit(\n request: FactoryTransitionRequest,\n transitionId: string,\n evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string },\n options: TransitionConsentOptions,\n // The registry the move was evaluated against; rejected moves never release resources.\n boards: BoardRegistry | undefined,\n ): Promise<FactoryTransitionResult> {\n const committed = await this.#storage.commitTransition({\n autonomy: options.autonomy,\n consentedBy: options.consentedBy,\n ...(options.accept ? { accept: true } : {}),\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n expectedRevision: request.expectedRevision,\n destinationStage: request.stage,\n actorId: actorId(request.actor),\n ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },\n configVersion: this.#configVersion,\n causalChain: [...(request.causalChain ?? [])],\n evaluation,\n ...(options.triageType ? { triageType: options.triageType } : {}),\n });\n if (committed.status === 'missing') {\n return rejection(transitionId, request.workItemId, 'invalid_transition', 'Work item not found.');\n }\n const result = committed.result as unknown as FactoryTransitionResult;\n if (\n this.#onAccepted &&\n options.accept &&\n committed.status === 'committed' &&\n result.status === 'accepted' &&\n committed.item?.acceptedAt\n ) {\n const item = committed.item;\n const onAccepted = this.#onAccepted;\n // Invoke inside the chain so a synchronous throw is isolated the same way an async rejection is.\n void Promise.resolve()\n .then(() =>\n onAccepted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n item,\n }),\n )\n .catch(error => {\n console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);\n });\n }\n // Only an installed board's declaration releases resources; an unknown board or phase never does.\n if (\n this.#onTerminalStage &&\n result.status === 'accepted' &&\n boards?.get(request.board)?.isTerminal(result.stage) === true\n ) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const cleanup = Promise.resolve(\n this.#onTerminalStage({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n stage: result.stage,\n revision: result.revision,\n actor: request.actor,\n }),\n );\n // A late rejection after the timeout wins the race must not surface\n // as an unhandled rejection.\n cleanup.catch(() => {});\n await Promise.race([\n cleanup,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, this.#terminalCleanupTimeoutMs);\n }),\n ]);\n } catch {\n // Resource release is best-effort — never fail a committed transition.\n } finally {\n clearTimeout(timer);\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
@@ -11,8 +11,11 @@ export interface LocalFactorySandboxOptions {
11
11
  * host variables that keep git and TLS working (HOME, USER, SHELL, locale,
12
12
  * SSL_CERT_*, …), so app secrets never reach commands run against untrusted
13
13
  * repository checkouts. PATH is always added by LocalSandbox itself.
14
+ *
15
+ * A function receives each session's context, so a credential can go to one
16
+ * factory's sessions only: `ctx => (ctx.factoryId === id ? { TOKEN } : {})`.
14
17
  */
15
- env?: Record<string, string>;
18
+ env?: Record<string, string> | ((ctx: FactorySandboxContext) => Record<string, string>);
16
19
  }
17
20
  /**
18
21
  * Sessions run on the host machine: each session's sandbox is a LocalSandbox
@@ -1 +1 @@
1
- {"version":3,"file":"local-factory-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/local-factory-sandbox.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AA2BD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;;IAC5E,QAAQ,CAAC,QAAQ,WAAW;IAK5B,YAAY,OAAO,GAAE,0BAA+B,EAMnD;IAED,MAAM,CAAC,GAAG,EAAE,qBAAqB,GAAG,YAAY,CAO/C;CACF"}
1
+ {"version":3,"file":"local-factory-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/local-factory-sandbox.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,qBAAqB,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACzF;AA2BD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;;IAC5E,QAAQ,CAAC,QAAQ,WAAW;IAK5B,YAAY,OAAO,GAAE,0BAA+B,EAMnD;IAED,MAAM,CAAC,GAAG,EAAE,qBAAqB,GAAG,YAAY,CAO/C;CACF"}
@@ -44,7 +44,7 @@ var LocalFactorySandbox = class extends FactorySandbox {
44
44
  id: ctx.sessionId,
45
45
  timeout: 3e4,
46
46
  workingDirectory: join(this.#root, ctx.sessionId),
47
- env: this.#env
47
+ env: typeof this.#env === "function" ? this.#env(ctx) : this.#env
48
48
  });
49
49
  }
50
50
  };
@@ -1 +1 @@
1
- {"version":3,"file":"local-factory-sandbox.js","names":[],"sources":["../../src/sandbox/local-factory-sandbox.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { FactorySandbox, LocalSandbox } from '@mastra/core/workspace';\nimport type { FactorySandboxContext } from '@mastra/core/workspace';\n\nexport interface LocalFactorySandboxOptions {\n /**\n * Directory under which every session gets its own working directory.\n * Defaults to `MASTRACODE_LOCAL_SANDBOX_ROOT`, else `~/.mastracode/web/sandboxes`.\n */\n root?: string;\n /**\n * Environment for commands run in the sandbox. Defaults to an allow-list of\n * host variables that keep git and TLS working (HOME, USER, SHELL, locale,\n * SSL_CERT_*, …), so app secrets never reach commands run against untrusted\n * repository checkouts. PATH is always added by LocalSandbox itself.\n */\n env?: Record<string, string>;\n}\n\nconst HOST_ENV_KEYS = [\n 'HOME',\n 'USER',\n 'LOGNAME',\n 'SHELL',\n 'TMPDIR',\n 'LANG',\n 'LC_ALL',\n 'TERM',\n 'TZ',\n 'GIT_EXEC_PATH',\n 'GIT_TEMPLATE_DIR',\n 'SSL_CERT_FILE',\n 'SSL_CERT_DIR',\n] as const;\n\nfunction hostEnv(): Record<string, string> {\n const env: Record<string, string> = {};\n for (const key of HOST_ENV_KEYS) {\n const value = process.env[key];\n if (value) env[key] = value;\n }\n return env;\n}\n\n/**\n * Sessions run on the host machine: each session's sandbox is a LocalSandbox\n * rooted at `join(root, sessionId)`, where the repository checks out as a\n * subdirectory. No template and no user-tunable settings.\n */\nexport class LocalFactorySandbox extends FactorySandbox<Record<string, never>> {\n readonly provider = 'local';\n\n readonly #root: string;\n readonly #env: Record<string, string>;\n\n constructor(options: LocalFactorySandboxOptions = {}) {\n super();\n this.#root =\n options.root ??\n (process.env.MASTRACODE_LOCAL_SANDBOX_ROOT?.trim() || join(homedir(), '.mastracode', 'web', 'sandboxes'));\n this.#env = options.env ?? hostEnv();\n }\n\n create(ctx: FactorySandboxContext): LocalSandbox {\n return new LocalSandbox({\n id: ctx.sessionId,\n timeout: 30_000,\n workingDirectory: join(this.#root, ctx.sessionId),\n env: this.#env,\n });\n }\n}\n"],"mappings":";;;;AAoBA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,UAAkC;CACzC,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,OAAO,eAAe;EAC/B,MAAM,QAAQ,QAAQ,IAAI;EAC1B,IAAI,OAAO,IAAI,OAAO;CACxB;CACA,OAAO;AACT;;;;;;AAOA,IAAa,sBAAb,cAAyC,eAAsC;CAC7E,WAAoB;CAEpB;CACA;CAEA,YAAY,UAAsC,CAAC,GAAG;EACpD,MAAM;EACN,KAAK,QACH,QAAQ,SACP,QAAQ,IAAI,+BAA+B,KAAK,KAAK,KAAK,QAAQ,GAAG,eAAe,OAAO,WAAW;EACzG,KAAK,OAAO,QAAQ,OAAO,QAAQ;CACrC;CAEA,OAAO,KAA0C;EAC/C,OAAO,IAAI,aAAa;GACtB,IAAI,IAAI;GACR,SAAS;GACT,kBAAkB,KAAK,KAAK,OAAO,IAAI,SAAS;GAChD,KAAK,KAAK;EACZ,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"local-factory-sandbox.js","names":[],"sources":["../../src/sandbox/local-factory-sandbox.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { FactorySandbox, LocalSandbox } from '@mastra/core/workspace';\nimport type { FactorySandboxContext } from '@mastra/core/workspace';\n\nexport interface LocalFactorySandboxOptions {\n /**\n * Directory under which every session gets its own working directory.\n * Defaults to `MASTRACODE_LOCAL_SANDBOX_ROOT`, else `~/.mastracode/web/sandboxes`.\n */\n root?: string;\n /**\n * Environment for commands run in the sandbox. Defaults to an allow-list of\n * host variables that keep git and TLS working (HOME, USER, SHELL, locale,\n * SSL_CERT_*, …), so app secrets never reach commands run against untrusted\n * repository checkouts. PATH is always added by LocalSandbox itself.\n *\n * A function receives each session's context, so a credential can go to one\n * factory's sessions only: `ctx => (ctx.factoryId === id ? { TOKEN } : {})`.\n */\n env?: Record<string, string> | ((ctx: FactorySandboxContext) => Record<string, string>);\n}\n\nconst HOST_ENV_KEYS = [\n 'HOME',\n 'USER',\n 'LOGNAME',\n 'SHELL',\n 'TMPDIR',\n 'LANG',\n 'LC_ALL',\n 'TERM',\n 'TZ',\n 'GIT_EXEC_PATH',\n 'GIT_TEMPLATE_DIR',\n 'SSL_CERT_FILE',\n 'SSL_CERT_DIR',\n] as const;\n\nfunction hostEnv(): Record<string, string> {\n const env: Record<string, string> = {};\n for (const key of HOST_ENV_KEYS) {\n const value = process.env[key];\n if (value) env[key] = value;\n }\n return env;\n}\n\n/**\n * Sessions run on the host machine: each session's sandbox is a LocalSandbox\n * rooted at `join(root, sessionId)`, where the repository checks out as a\n * subdirectory. No template and no user-tunable settings.\n */\nexport class LocalFactorySandbox extends FactorySandbox<Record<string, never>> {\n readonly provider = 'local';\n\n readonly #root: string;\n readonly #env: NonNullable<LocalFactorySandboxOptions['env']>;\n\n constructor(options: LocalFactorySandboxOptions = {}) {\n super();\n this.#root =\n options.root ??\n (process.env.MASTRACODE_LOCAL_SANDBOX_ROOT?.trim() || join(homedir(), '.mastracode', 'web', 'sandboxes'));\n this.#env = options.env ?? hostEnv();\n }\n\n create(ctx: FactorySandboxContext): LocalSandbox {\n return new LocalSandbox({\n id: ctx.sessionId,\n timeout: 30_000,\n workingDirectory: join(this.#root, ctx.sessionId),\n env: typeof this.#env === 'function' ? this.#env(ctx) : this.#env,\n });\n }\n}\n"],"mappings":";;;;AAuBA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,UAAkC;CACzC,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,OAAO,eAAe;EAC/B,MAAM,QAAQ,QAAQ,IAAI;EAC1B,IAAI,OAAO,IAAI,OAAO;CACxB;CACA,OAAO;AACT;;;;;;AAOA,IAAa,sBAAb,cAAyC,eAAsC;CAC7E,WAAoB;CAEpB;CACA;CAEA,YAAY,UAAsC,CAAC,GAAG;EACpD,MAAM;EACN,KAAK,QACH,QAAQ,SACP,QAAQ,IAAI,+BAA+B,KAAK,KAAK,KAAK,QAAQ,GAAG,eAAe,OAAO,WAAW;EACzG,KAAK,OAAO,QAAQ,OAAO,QAAQ;CACrC;CAEA,OAAO,KAA0C;EAC/C,OAAO,IAAI,aAAa;GACtB,IAAI,IAAI;GACR,SAAS;GACT,kBAAkB,KAAK,KAAK,OAAO,IAAI,SAAS;GAChD,KAAK,OAAO,KAAK,SAAS,aAAa,KAAK,KAAK,GAAG,IAAI,KAAK;EAC/D,CAAC;CACH;AACF"}
@@ -9,6 +9,7 @@ export declare const AUDIT_ACTIONS: {
9
9
  readonly agent: readonly ['commit', 'push', 'pr_opened', 'signaled'];
10
10
  readonly intake: readonly ['config_updated', 'binding_updated', 'label_route_updated'];
11
11
  readonly session: readonly ['owner_transferred'];
12
+ readonly board: readonly ['created', 'updated', 'deleted'];
12
13
  };
13
14
  export type AuditNamespace = keyof typeof AUDIT_ACTIONS;
14
15
  export type AuditAction = {
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/actions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,aAAa;aACxB,SAAS,YACP,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB;aAErB,GAAG,YAAG,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU;aAChF,GAAG,YAAG,QAAQ,EAAE,MAAM,EAAE,WAAW;aACnC,KAAK,YAAG,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU;aACjD,MAAM,YAAG,gBAAgB,EAAE,iBAAiB,EAAE,qBAAqB;aACnE,OAAO,YAAG,mBAAmB;CACrB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,aAAa,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;KACvB,SAAS,IAAI,cAAc,GAAG,WAAW,SAAS,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE;CACnG,CAAC,cAAc,CAAC,CAAC;AAElB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAEvE;AAED,wBAAgB,eAAe,IAAI,cAAc,EAAE,CAElD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIxG;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAGjE;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAExF"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/actions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,aAAa;aACxB,SAAS,YACP,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB;aAErB,GAAG,YAAG,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU;aAChF,GAAG,YAAG,QAAQ,EAAE,MAAM,EAAE,WAAW;aACnC,KAAK,YAAG,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU;aACjD,MAAM,YAAG,gBAAgB,EAAE,iBAAiB,EAAE,qBAAqB;aACnE,OAAO,YAAG,mBAAmB;aAC7B,KAAK,YAAG,SAAS,EAAE,SAAS,EAAE,SAAS;CAC/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,aAAa,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;KACvB,SAAS,IAAI,cAAc,GAAG,WAAW,SAAS,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE;CACnG,CAAC,cAAc,CAAC,CAAC;AAElB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAEvE;AAED,wBAAgB,eAAe,IAAI,cAAc,EAAE,CAElD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIxG;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAGjE;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAExF"}
@@ -41,7 +41,12 @@ const AUDIT_ACTIONS = {
41
41
  "binding_updated",
42
42
  "label_route_updated"
43
43
  ],
44
- session: ["owner_transferred"]
44
+ session: ["owner_transferred"],
45
+ board: [
46
+ "created",
47
+ "updated",
48
+ "deleted"
49
+ ]
45
50
  };
46
51
  function isAuditNamespace(value) {
47
52
  return Object.hasOwn(AUDIT_ACTIONS, value);
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","names":[],"sources":["../../../../src/storage/domains/audit/actions.ts"],"sourcesContent":["/**\n * Every action the trail can hold, by namespace. Register new ones in the\n * WorkOS dashboard under Audit Logs → Events before the mirror accepts them.\n */\nexport const AUDIT_ACTIONS = {\n work_item: [\n 'created',\n 'updated',\n 'deleted',\n 'stage_moved',\n 'transition_rejected',\n 'comment_created',\n 'comment_edited',\n 'comment_deleted',\n 'comment_mentioned',\n 'labels_reconciled',\n ],\n run: ['started', 'ended', 'approved', 'dismissed', 'retry', 'queued', 'rejected'],\n git: ['commit', 'push', 'pr_opened'],\n agent: ['commit', 'push', 'pr_opened', 'signaled'],\n intake: ['config_updated', 'binding_updated', 'label_route_updated'],\n session: ['owner_transferred'],\n} as const;\n\nexport type AuditNamespace = keyof typeof AUDIT_ACTIONS;\n\nexport type AuditAction = {\n [Namespace in AuditNamespace]: `factory.${Namespace}.${(typeof AUDIT_ACTIONS)[Namespace][number]}`;\n}[AuditNamespace];\n\nexport function isAuditNamespace(value: string): value is AuditNamespace {\n return Object.hasOwn(AUDIT_ACTIONS, value);\n}\n\nexport function auditNamespaces(): AuditNamespace[] {\n return Object.keys(AUDIT_ACTIONS).filter(isAuditNamespace);\n}\n\nexport function parseAuditAction(action: string): { namespace: AuditNamespace; leaf: string } | undefined {\n const [prefix, namespace, leaf, ...rest] = action.split('.');\n if (prefix !== 'factory' || namespace === undefined || leaf === undefined || rest.length > 0) return undefined;\n return isAuditNamespace(namespace) ? { namespace, leaf } : undefined;\n}\n\nexport function isAuditAction(value: string): value is AuditAction {\n const parsed = parseAuditAction(value);\n return parsed !== undefined && AUDIT_ACTIONS[parsed.namespace].some(leaf => leaf === parsed.leaf);\n}\n\nexport function auditActionsInNamespaces(namespaces: readonly AuditNamespace[]): string[] {\n return namespaces.flatMap(namespace => AUDIT_ACTIONS[namespace].map(leaf => `factory.${namespace}.${leaf}`));\n}\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB;CAC3B,WAAW;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,KAAK;EAAC;EAAW;EAAS;EAAY;EAAa;EAAS;EAAU;CAAU;CAChF,KAAK;EAAC;EAAU;EAAQ;CAAW;CACnC,OAAO;EAAC;EAAU;EAAQ;EAAa;CAAU;CACjD,QAAQ;EAAC;EAAkB;EAAmB;CAAqB;CACnE,SAAS,CAAC,mBAAmB;AAC/B;AAQA,SAAgB,iBAAiB,OAAwC;CACvE,OAAO,OAAO,OAAO,eAAe,KAAK;AAC3C;AAEA,SAAgB,kBAAoC;CAClD,OAAO,OAAO,KAAK,aAAa,CAAC,CAAC,OAAO,gBAAgB;AAC3D;AAEA,SAAgB,iBAAiB,QAAyE;CACxG,MAAM,CAAC,QAAQ,WAAW,MAAM,GAAG,QAAQ,OAAO,MAAM,GAAG;CAC3D,IAAI,WAAW,aAAa,cAAc,KAAA,KAAa,SAAS,KAAA,KAAa,KAAK,SAAS,GAAG,OAAO,KAAA;CACrG,OAAO,iBAAiB,SAAS,IAAI;EAAE;EAAW;CAAK,IAAI,KAAA;AAC7D;AAEA,SAAgB,cAAc,OAAqC;CACjE,MAAM,SAAS,iBAAiB,KAAK;CACrC,OAAO,WAAW,KAAA,KAAa,cAAc,OAAO,UAAU,CAAC,MAAK,SAAQ,SAAS,OAAO,IAAI;AAClG;AAEA,SAAgB,yBAAyB,YAAiD;CACxF,OAAO,WAAW,SAAQ,cAAa,cAAc,UAAU,CAAC,KAAI,SAAQ,WAAW,UAAU,GAAG,MAAM,CAAC;AAC7G"}
1
+ {"version":3,"file":"actions.js","names":[],"sources":["../../../../src/storage/domains/audit/actions.ts"],"sourcesContent":["/**\n * Every action the trail can hold, by namespace. Register new ones in the\n * WorkOS dashboard under Audit Logs → Events before the mirror accepts them.\n */\nexport const AUDIT_ACTIONS = {\n work_item: [\n 'created',\n 'updated',\n 'deleted',\n 'stage_moved',\n 'transition_rejected',\n 'comment_created',\n 'comment_edited',\n 'comment_deleted',\n 'comment_mentioned',\n 'labels_reconciled',\n ],\n run: ['started', 'ended', 'approved', 'dismissed', 'retry', 'queued', 'rejected'],\n git: ['commit', 'push', 'pr_opened'],\n agent: ['commit', 'push', 'pr_opened', 'signaled'],\n intake: ['config_updated', 'binding_updated', 'label_route_updated'],\n session: ['owner_transferred'],\n board: ['created', 'updated', 'deleted'],\n} as const;\n\nexport type AuditNamespace = keyof typeof AUDIT_ACTIONS;\n\nexport type AuditAction = {\n [Namespace in AuditNamespace]: `factory.${Namespace}.${(typeof AUDIT_ACTIONS)[Namespace][number]}`;\n}[AuditNamespace];\n\nexport function isAuditNamespace(value: string): value is AuditNamespace {\n return Object.hasOwn(AUDIT_ACTIONS, value);\n}\n\nexport function auditNamespaces(): AuditNamespace[] {\n return Object.keys(AUDIT_ACTIONS).filter(isAuditNamespace);\n}\n\nexport function parseAuditAction(action: string): { namespace: AuditNamespace; leaf: string } | undefined {\n const [prefix, namespace, leaf, ...rest] = action.split('.');\n if (prefix !== 'factory' || namespace === undefined || leaf === undefined || rest.length > 0) return undefined;\n return isAuditNamespace(namespace) ? { namespace, leaf } : undefined;\n}\n\nexport function isAuditAction(value: string): value is AuditAction {\n const parsed = parseAuditAction(value);\n return parsed !== undefined && AUDIT_ACTIONS[parsed.namespace].some(leaf => leaf === parsed.leaf);\n}\n\nexport function auditActionsInNamespaces(namespaces: readonly AuditNamespace[]): string[] {\n return namespaces.flatMap(namespace => AUDIT_ACTIONS[namespace].map(leaf => `factory.${namespace}.${leaf}`));\n}\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB;CAC3B,WAAW;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,KAAK;EAAC;EAAW;EAAS;EAAY;EAAa;EAAS;EAAU;CAAU;CAChF,KAAK;EAAC;EAAU;EAAQ;CAAW;CACnC,OAAO;EAAC;EAAU;EAAQ;EAAa;CAAU;CACjD,QAAQ;EAAC;EAAkB;EAAmB;CAAqB;CACnE,SAAS,CAAC,mBAAmB;CAC7B,OAAO;EAAC;EAAW;EAAW;CAAS;AACzC;AAQA,SAAgB,iBAAiB,OAAwC;CACvE,OAAO,OAAO,OAAO,eAAe,KAAK;AAC3C;AAEA,SAAgB,kBAAoC;CAClD,OAAO,OAAO,KAAK,aAAa,CAAC,CAAC,OAAO,gBAAgB;AAC3D;AAEA,SAAgB,iBAAiB,QAAyE;CACxG,MAAM,CAAC,QAAQ,WAAW,MAAM,GAAG,QAAQ,OAAO,MAAM,GAAG;CAC3D,IAAI,WAAW,aAAa,cAAc,KAAA,KAAa,SAAS,KAAA,KAAa,KAAK,SAAS,GAAG,OAAO,KAAA;CACrG,OAAO,iBAAiB,SAAS,IAAI;EAAE;EAAW;CAAK,IAAI,KAAA;AAC7D;AAEA,SAAgB,cAAc,OAAqC;CACjE,MAAM,SAAS,iBAAiB,KAAK;CACrC,OAAO,WAAW,KAAA,KAAa,cAAc,OAAO,UAAU,CAAC,MAAK,SAAQ,SAAS,OAAO,IAAI;AAClG;AAEA,SAAgB,yBAAyB,YAAiD;CACxF,OAAO,WAAW,SAAQ,cAAa,cAAc,UAAU,CAAC,KAAI,SAAQ,WAAW,UAAU,GAAG,MAAM,CAAC;AAC7G"}
@@ -0,0 +1,110 @@
1
+ import { FactoryStorageDomain } from '@mastra/core/storage';
2
+ import type { RuleGroup } from '../../../rules/rule-group.js';
3
+ /** The project a board belongs to. */
4
+ export interface StoredBoardScope {
5
+ orgId: string;
6
+ factoryProjectId: string;
7
+ }
8
+ export type StoredPhaseKind = 'resting' | 'working' | 'terminal';
9
+ /** Who runs a working phase. Factory runs every phase with its code agent. */
10
+ export interface StoredPhaseRunner {
11
+ skill?: string;
12
+ context?: string;
13
+ instructions?: string;
14
+ }
15
+ export interface StoredPhaseExitTool {
16
+ id: string;
17
+ autoApprove?: boolean;
18
+ autoApproveWhen?: RuleGroup;
19
+ }
20
+ /** A phase as it is written. `key` is the stage id cards sit in. */
21
+ export interface StoredPhaseInput {
22
+ key: string;
23
+ name: string;
24
+ description?: string | null;
25
+ kind: StoredPhaseKind;
26
+ runner?: StoredPhaseRunner | null;
27
+ /** Unset or `null` on a resting or working phase means every other phase; `[]` means none. */
28
+ nextPhases?: string[] | null;
29
+ autoStart?: boolean;
30
+ autoStartWhen?: RuleGroup | null;
31
+ exitTool?: StoredPhaseExitTool | null;
32
+ }
33
+ /** A board as it is written. Phase order is significant: the first phase is where cards start. */
34
+ export interface StoredBoardInput {
35
+ key: string;
36
+ name: string;
37
+ description?: string | null;
38
+ phases: StoredPhaseInput[];
39
+ }
40
+ export interface StoredPhaseRecord {
41
+ key: string;
42
+ position: number;
43
+ name: string;
44
+ description: string | null;
45
+ kind: StoredPhaseKind;
46
+ runner: StoredPhaseRunner | null;
47
+ nextPhases: string[] | null;
48
+ autoStart: boolean;
49
+ autoStartWhen: RuleGroup | null;
50
+ exitTool: StoredPhaseExitTool | null;
51
+ }
52
+ export interface StoredBoardRecord {
53
+ id: string;
54
+ orgId: string;
55
+ factoryProjectId: string;
56
+ key: string;
57
+ name: string;
58
+ description: string | null;
59
+ position: number;
60
+ revision: number;
61
+ createdBy: string;
62
+ createdAt: Date;
63
+ updatedBy: string;
64
+ updatedAt: Date;
65
+ phases: StoredPhaseRecord[];
66
+ }
67
+ export interface StoredBoardSnapshot {
68
+ version: string | null;
69
+ boards: StoredBoardRecord[];
70
+ }
71
+ export type StoredBoardErrorCode = 'board_exists' | 'board_not_found' | 'revision_mismatch';
72
+ export declare class StoredBoardError extends Error {
73
+ readonly code: StoredBoardErrorCode;
74
+ name: string;
75
+ constructor(code: StoredBoardErrorCode, message: string);
76
+ }
77
+ /**
78
+ * Each project's boards and their ordered phases. Every write runs in one transaction that also
79
+ * replaces the project's row in `factory_board_definition_versions`, so caches on other
80
+ * replicas can detect it.
81
+ */
82
+ export declare class StoredBoardsStorage extends FactoryStorageDomain {
83
+ #private;
84
+ constructor();
85
+ init(): Promise<void>;
86
+ dangerouslyClearAll(): Promise<void>;
87
+ /** The project's current definition version; null before its first board write. */
88
+ readVersion(scope: StoredBoardScope): Promise<string | null>;
89
+ /** The project's boards in order. */
90
+ list(scope: StoredBoardScope): Promise<StoredBoardRecord[]>;
91
+ /**
92
+ * The version and the boards, read in that order: a write landing between the two reads leaves a
93
+ * version older than the rows, so a cache keyed on it rereads on its next check instead of
94
+ * keeping the stale rows.
95
+ */
96
+ snapshot(scope: StoredBoardScope): Promise<StoredBoardSnapshot>;
97
+ get(scope: StoredBoardScope, key: string): Promise<StoredBoardRecord | null>;
98
+ /** Creates a board through the API. */
99
+ create(scope: StoredBoardScope, input: StoredBoardInput, actor: string): Promise<StoredBoardRecord>;
100
+ /** Replaces a board's name, description and phases. The board key never changes. */
101
+ replace(scope: StoredBoardScope, key: string, input: Omit<StoredBoardInput, 'key'>, options: {
102
+ expectedRevision: number;
103
+ actor: string;
104
+ }): Promise<StoredBoardRecord>;
105
+ /** Deletes a board and its phases. */
106
+ delete(scope: StoredBoardScope, key: string, options: {
107
+ expectedRevision: number;
108
+ }): Promise<void>;
109
+ }
110
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/boards/base.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAGlF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAO9D,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAEjE,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,oEAAoE;AACpE,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACvC;AAED,kGAAkG;AAClG,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,SAAS,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAE5F,qBAAa,gBAAiB,SAAQ,KAAK;IAGvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB;IAF5B,IAAI,SAAsB;IACnC,YACW,IAAI,EAAE,oBAAoB,EACnC,OAAO,EAAE,MAAM,EAGhB;CACF;AA+ED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,oBAAoB;;IAC3D,cAEC;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1B;IAEK,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIzC;IAED,mFAAmF;IAC7E,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEjE;IAED,qCAAqC;IAC/B,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAEhE;IAED;;;;OAIG;IACG,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAGpE;IAEK,GAAG,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAKjF;IAED,uCAAuC;IACjC,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkBxG;IAED,oFAAoF;IAC9E,OAAO,CACX,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACpC,OAAO,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACnD,OAAO,CAAC,iBAAiB,CAAC,CAQ5B;IAED,sCAAsC;IAChC,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASvG;CA2BF"}