@mastra/factory 0.21.0-alpha.7 → 0.21.0-alpha.9

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 (131) hide show
  1. package/README.md +72 -1
  2. package/dist/boards/define-board.d.ts +36 -1
  3. package/dist/boards/define-board.d.ts.map +1 -1
  4. package/dist/boards/define-board.js +46 -0
  5. package/dist/boards/define-board.js.map +1 -1
  6. package/dist/boards/index.d.ts +2 -2
  7. package/dist/boards/index.d.ts.map +1 -1
  8. package/dist/boards/index.js +2 -2
  9. package/dist/boards/index.js.map +1 -1
  10. package/dist/boards/semantics.d.ts +34 -1
  11. package/dist/boards/semantics.d.ts.map +1 -1
  12. package/dist/boards/semantics.js +65 -1
  13. package/dist/boards/semantics.js.map +1 -1
  14. package/dist/boards/work.d.ts.map +1 -1
  15. package/dist/boards/work.js +5 -1
  16. package/dist/boards/work.js.map +1 -1
  17. package/dist/factory.d.ts.map +1 -1
  18. package/dist/factory.js +8 -0
  19. package/dist/factory.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/integrations/github/default-rules.d.ts +2 -2
  23. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  24. package/dist/integrations/github/default-rules.js +11 -8
  25. package/dist/integrations/github/default-rules.js.map +1 -1
  26. package/dist/integrations/github/integration.d.ts +18 -0
  27. package/dist/integrations/github/integration.d.ts.map +1 -1
  28. package/dist/integrations/github/integration.js +53 -2
  29. package/dist/integrations/github/integration.js.map +1 -1
  30. package/dist/integrations/github/routes.d.ts +12 -0
  31. package/dist/integrations/github/routes.d.ts.map +1 -1
  32. package/dist/integrations/github/routes.js +63 -1
  33. package/dist/integrations/github/routes.js.map +1 -1
  34. package/dist/integrations/github/rules.d.ts +5 -2
  35. package/dist/integrations/github/rules.d.ts.map +1 -1
  36. package/dist/integrations/github/rules.js +72 -19
  37. package/dist/integrations/github/rules.js.map +1 -1
  38. package/dist/integrations/gitlab/default-rules.d.ts +3 -3
  39. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -1
  40. package/dist/integrations/gitlab/default-rules.js +13 -6
  41. package/dist/integrations/gitlab/default-rules.js.map +1 -1
  42. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/rules.js +39 -8
  44. package/dist/integrations/gitlab/rules.js.map +1 -1
  45. package/dist/integrations/incidentio/default-rules.d.ts +3 -3
  46. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -1
  47. package/dist/integrations/incidentio/default-rules.js +8 -6
  48. package/dist/integrations/incidentio/default-rules.js.map +1 -1
  49. package/dist/integrations/incidentio/rules.d.ts.map +1 -1
  50. package/dist/integrations/incidentio/rules.js +6 -3
  51. package/dist/integrations/incidentio/rules.js.map +1 -1
  52. package/dist/integrations/jira/default-rules.d.ts +3 -3
  53. package/dist/integrations/jira/default-rules.d.ts.map +1 -1
  54. package/dist/integrations/jira/default-rules.js +8 -6
  55. package/dist/integrations/jira/default-rules.js.map +1 -1
  56. package/dist/integrations/jira/rules.d.ts.map +1 -1
  57. package/dist/integrations/jira/rules.js +6 -3
  58. package/dist/integrations/jira/rules.js.map +1 -1
  59. package/dist/integrations/linear/default-rules.d.ts +3 -3
  60. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  61. package/dist/integrations/linear/default-rules.js +8 -6
  62. package/dist/integrations/linear/default-rules.js.map +1 -1
  63. package/dist/integrations/linear/routes.js +1 -1
  64. package/dist/integrations/linear/rules.d.ts.map +1 -1
  65. package/dist/integrations/linear/rules.js +6 -3
  66. package/dist/integrations/linear/rules.js.map +1 -1
  67. package/dist/integrations/pull-request-head.d.ts +18 -0
  68. package/dist/integrations/pull-request-head.d.ts.map +1 -0
  69. package/dist/integrations/pull-request-head.js +45 -0
  70. package/dist/integrations/pull-request-head.js.map +1 -0
  71. package/dist/routes/attention-providers.js +3 -3
  72. package/dist/routes/attention-providers.js.map +1 -1
  73. package/dist/routes/contracts.js +1 -1
  74. package/dist/routes/intake.d.ts +3 -0
  75. package/dist/routes/intake.d.ts.map +1 -1
  76. package/dist/routes/intake.js +47 -6
  77. package/dist/routes/intake.js.map +1 -1
  78. package/dist/routes/surface.d.ts.map +1 -1
  79. package/dist/routes/surface.js +3 -2
  80. package/dist/routes/surface.js.map +1 -1
  81. package/dist/routes/work-items.d.ts.map +1 -1
  82. package/dist/routes/work-items.js +3 -4
  83. package/dist/routes/work-items.js.map +1 -1
  84. package/dist/rules/decision-applicability.d.ts +6 -0
  85. package/dist/rules/decision-applicability.d.ts.map +1 -1
  86. package/dist/rules/decision-applicability.js +10 -1
  87. package/dist/rules/decision-applicability.js.map +1 -1
  88. package/dist/rules/dispatcher.d.ts.map +1 -1
  89. package/dist/rules/dispatcher.js +20 -15
  90. package/dist/rules/dispatcher.js.map +1 -1
  91. package/dist/rules/event-rules.d.ts +23 -0
  92. package/dist/rules/event-rules.d.ts.map +1 -0
  93. package/dist/rules/event-rules.js +46 -0
  94. package/dist/rules/event-rules.js.map +1 -0
  95. package/dist/rules/index.js +1 -1
  96. package/dist/rules/processor.js +1 -1
  97. package/dist/rules/review-source-tool.d.ts +5 -3
  98. package/dist/rules/review-source-tool.d.ts.map +1 -1
  99. package/dist/rules/review-source-tool.js.map +1 -1
  100. package/dist/rules/tools.js +1 -1
  101. package/dist/rules/transition-service.js +1 -1
  102. package/dist/rules/types.d.ts +47 -0
  103. package/dist/rules/types.d.ts.map +1 -1
  104. package/dist/rules/types.js.map +1 -1
  105. package/dist/storage/domains/comments/base.d.ts +3 -0
  106. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  107. package/dist/storage/domains/comments/base.js +8 -1
  108. package/dist/storage/domains/comments/base.js.map +1 -1
  109. package/dist/storage/domains/work-items/base.d.ts +7 -3
  110. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  111. package/dist/storage/domains/work-items/base.js +66 -16
  112. package/dist/storage/domains/work-items/base.js.map +1 -1
  113. package/dist/storage/domains/work-items/board-version.d.ts +26 -0
  114. package/dist/storage/domains/work-items/board-version.d.ts.map +1 -0
  115. package/dist/storage/domains/work-items/board-version.js +110 -0
  116. package/dist/storage/domains/work-items/board-version.js.map +1 -0
  117. package/dist/supervisor/health.d.ts.map +1 -1
  118. package/dist/supervisor/health.js +17 -5
  119. package/dist/supervisor/health.js.map +1 -1
  120. package/dist/supervisor/read-tools.d.ts.map +1 -1
  121. package/dist/supervisor/read-tools.js +13 -8
  122. package/dist/supervisor/read-tools.js.map +1 -1
  123. package/dist/supervisor/write-tools.d.ts.map +1 -1
  124. package/dist/supervisor/write-tools.js +5 -1
  125. package/dist/supervisor/write-tools.js.map +1 -1
  126. package/dist/workspace.d.ts.map +1 -1
  127. package/dist/workspace.js +17 -1
  128. package/dist/workspace.js.map +1 -1
  129. package/factory-skills/factory-rereview/SKILL.md +4 -4
  130. package/factory-skills/factory-review/SKILL.md +2 -2
  131. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/gitlab/default-rules.ts"],"sourcesContent":["import type { FactoryGitLabEventName, FactoryGitLabRuleContext, FactoryRuleHandler } from '../../rules/types.js';\n\nexport type GitLabRuleOverrides = Partial<\n Record<FactoryGitLabEventName, FactoryRuleHandler<FactoryGitLabRuleContext> | null | undefined>\n>;\nexport type GitLabEventRules = Readonly<\n Record<FactoryGitLabEventName, FactoryRuleHandler<FactoryGitLabRuleContext> | null>\n>;\n\nfunction actorUsername(context: Pick<FactoryGitLabRuleContext, 'actor'>): string | undefined {\n return context.actor.type === 'gitlab' ? context.actor.username : undefined;\n}\n\nfunction trustedGitLabActor(context: Pick<FactoryGitLabRuleContext, 'actor'>): boolean {\n return context.actor.type === 'gitlab' && context.actor.trusted;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGitLabRuleContext) {\n if (!context.issue) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: context.intake?.board ?? 'work',\n source: 'gitlab-issue',\n sourceKey: context.issue.sourceKey,\n title: context.issue.title,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'intake',\n metadata: {\n gitlabHost: context.repository.host,\n gitlabProjectId: context.repository.id,\n gitlabIssueIid: context.issue.number,\n identifier: `${context.repository.fullName}#${context.issue.number}`,\n ...(context.issue.createdAt ? { sourceCreatedAt: context.issue.createdAt } : {}),\n ...((context.issue.author ?? actorUsername(context))\n ? { author: context.issue.author ?? actorUsername(context) }\n : {}),\n authorTrusted: context.issue.authorTrusted,\n autoStartCandidate:\n trustedGitLabActor(context) &&\n context.issue.authorTrusted &&\n createdAfterFactory(context.issue.createdAt, context.factory.createdAt),\n assignees: context.issue.assignees ?? [],\n labels: context.issue.labels ?? [],\n labelColors: context.issue.labelColors ?? {},\n },\n } as const;\n}\n\nfunction retriageIssue(context: FactoryGitLabRuleContext) {\n if (!context.item || context.item.source !== 'gitlab-issue' || !context.item.url) return;\n if (context.actor.type === 'gitlab' && context.actor.factoryAuthored) return;\n const reason = context.event === 'issueCommented' ? 'a comment was added' : 'the issue changed';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitLab issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction issueClosed(context: FactoryGitLabRuleContext) {\n if (!context.item || context.item.source !== 'gitlab-issue' || context.board !== 'work') return;\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: 'done',\n message: {\n text: `GitLab issue #${context.issue?.number ?? ''} was closed; this Work card was moved to Done.`,\n },\n } as const;\n}\n\nfunction materializeMergeRequest(context: FactoryGitLabRuleContext) {\n if (!context.mergeRequest) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:merge-request-intake`,\n board: 'review',\n source: 'gitlab-pr',\n sourceKey: context.mergeRequest.sourceKey,\n title: context.mergeRequest.title,\n url: context.mergeRequest.url,\n stage: 'intake',\n metadata: {\n gitlabHost: context.repository.host,\n gitlabProjectId: context.repository.id,\n gitlabMergeRequestIid: context.mergeRequest.number,\n ...(context.mergeRequest.createdAt ? { sourceCreatedAt: context.mergeRequest.createdAt } : {}),\n factoryAuthored: context.mergeRequest.factoryAuthored,\n authorTrusted: context.mergeRequest.authorTrusted,\n autoStartCandidate:\n ((trustedGitLabActor(context) && context.mergeRequest.authorTrusted) || context.mergeRequest.factoryAuthored) &&\n createdAfterFactory(context.mergeRequest.createdAt, context.factory.createdAt),\n state: context.mergeRequest.state,\n draft: context.mergeRequest.draft,\n merged: context.mergeRequest.merged,\n assignees: context.mergeRequest.assignees ?? [],\n requestedReviewers: context.mergeRequest.reviewers ?? [],\n labels: context.mergeRequest.labels ?? [],\n labelColors: context.mergeRequest.labelColors ?? {},\n headBranch: context.mergeRequest.headBranch,\n baseBranch: context.mergeRequest.baseBranch,\n ...(context.mergeRequest.author ? { author: context.mergeRequest.author } : {}),\n },\n } as const;\n}\n\nfunction mergeRequestUpdated(context: FactoryGitLabRuleContext) {\n if (!context.item || context.item.source !== 'gitlab-pr' || context.board !== 'review') return;\n if (!context.mergeRequest || context.mergeRequest.state !== 'open' || context.mergeRequest.merged) return;\n if (context.item.stages.some(stage => stage === 'intake')) return;\n // An untrusted push must not start review passes on demand.\n if (!trustedGitLabActor(context) && !context.mergeRequest.factoryAuthored) return;\n const alreadyReviewing = context.item.stages.some(stage => stage === 'review');\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-updated`,\n board: 'review',\n stage: 'review',\n ...(alreadyReviewing ? { reenter: true } : {}),\n } as const;\n}\n\nfunction mergeRequestCommented(context: FactoryGitLabRuleContext) {\n if (!context.item || !context.mergeRequest || !context.note || context.board !== 'work') return;\n if (context.mergeRequest.state !== 'open' || context.mergeRequest.merged) return;\n if (context.actor.type === 'gitlab' && context.actor.factoryAuthored) return;\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-merge-request-comment`,\n role: 'work',\n priority: 'high',\n message:\n `${context.note.author ?? 'Someone'} commented on GitLab merge request !${context.mergeRequest.number} ` +\n `(${context.note.url ?? context.mergeRequest.url}). Read the comment, address it if you agree, and push ` +\n 'the fixes to the merge-request branch. Reply in GitLab to anything you are deliberately not changing.',\n } as const;\n}\n\nfunction mergeRequestMerged(context: FactoryGitLabRuleContext) {\n if (!context.item || !context.mergeRequest?.merged) return;\n if (context.board === 'review') {\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:merge-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text: `GitLab merge request !${context.mergeRequest.number} merged; this Review card was moved to Done.`,\n },\n } as const;\n }\n if (context.board !== 'work') return;\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `GitLab merge request !${context.mergeRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this merge request merged.',\n } as const;\n}\n\nfunction mergeRequestClosed(context: FactoryGitLabRuleContext) {\n if (!context.item || !context.mergeRequest || context.mergeRequest.merged || context.board !== 'review') return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:merge-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `GitLab merge request !${context.mergeRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nexport const defaultGitLabRules = Object.freeze({\n issueOpened,\n issueUpdated: retriageIssue,\n issueClosed,\n issueCommented: retriageIssue,\n mergeRequestOpened: materializeMergeRequest,\n mergeRequestUpdated,\n mergeRequestCommented,\n mergeRequestMerged,\n mergeRequestClosed,\n} satisfies GitLabEventRules);\n\nexport function resolveGitLabRules(overrides?: GitLabRuleOverrides): GitLabEventRules {\n if (overrides !== undefined && (overrides === null || typeof overrides !== 'object' || Array.isArray(overrides))) {\n throw new Error('GitLab rules must be an object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryGitLabRuleContext> | null> = { ...defaultGitLabRules };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultGitLabRules, key)) {\n throw new Error(`Unknown GitLab rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryGitLabEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`GitLab rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return Object.freeze(rules) as GitLabEventRules;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/gitlab/default-rules.ts"],"sourcesContent":["import { recordExplicitRuleOverrides } from '../../rules/event-rules.js';\nimport { factoryRuleStage, isTerminalFactoryRuleStage } from '../../rules/types.js';\nimport type { FactoryGitLabEventName, FactoryGitLabRuleContext, FactoryRuleHandler } from '../../rules/types.js';\n\nexport type GitLabRuleOverrides = Partial<\n Record<FactoryGitLabEventName, FactoryRuleHandler<FactoryGitLabRuleContext> | null | undefined>\n>;\nexport type GitLabEventRules = Readonly<\n Record<FactoryGitLabEventName, FactoryRuleHandler<FactoryGitLabRuleContext> | null>\n>;\n\nfunction actorUsername(context: Pick<FactoryGitLabRuleContext, 'actor'>): string | undefined {\n return context.actor.type === 'gitlab' ? context.actor.username : undefined;\n}\n\nfunction trustedGitLabActor(context: Pick<FactoryGitLabRuleContext, 'actor'>): boolean {\n return context.actor.type === 'gitlab' && context.actor.trusted;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGitLabRuleContext) {\n if (!context.issue) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: context.intake?.board ?? 'work',\n source: 'gitlab-issue',\n sourceKey: context.issue.sourceKey,\n title: context.issue.title,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'intake',\n metadata: {\n gitlabHost: context.repository.host,\n gitlabProjectId: context.repository.id,\n gitlabIssueIid: context.issue.number,\n identifier: `${context.repository.fullName}#${context.issue.number}`,\n ...(context.issue.createdAt ? { sourceCreatedAt: context.issue.createdAt } : {}),\n ...((context.issue.author ?? actorUsername(context))\n ? { author: context.issue.author ?? actorUsername(context) }\n : {}),\n authorTrusted: context.issue.authorTrusted,\n autoStartCandidate:\n trustedGitLabActor(context) &&\n context.issue.authorTrusted &&\n createdAfterFactory(context.issue.createdAt, context.factory.createdAt),\n assignees: context.issue.assignees ?? [],\n labels: context.issue.labels ?? [],\n labelColors: context.issue.labelColors ?? {},\n },\n } as const;\n}\n\nfunction retriageIssue(context: FactoryGitLabRuleContext) {\n if (!context.item || context.item.source !== 'gitlab-issue' || !context.item.url) return;\n if (context.actor.type === 'gitlab' && context.actor.factoryAuthored) return;\n // The stage checks below use Work lane names; other boards opt in with their own rules.\n if (context.board !== 'work') return;\n // Re-triage would take over a card that is being built or reviewed.\n const stage = factoryRuleStage(context.item.stages);\n if (stage === 'execute' || stage === 'review') return;\n // A finished card has nothing left to triage.\n if (isTerminalFactoryRuleStage(context.item.stages)) return;\n const reason = context.event === 'issueCommented' ? 'a comment was added' : 'the issue changed';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitLab issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction issueClosed(context: FactoryGitLabRuleContext) {\n if (!context.item || context.item.source !== 'gitlab-issue') return;\n // The card's board decides where a closed source sends it; boards that declare no\n // `sourceClosed` mapping keep the card where it is, as do cards already finished.\n const closure = context.boardClosure;\n if (!context.board || !closure || closure.itemTerminal) return;\n // GitLab reports no close reason, so every closure counts as completed.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: context.board,\n stage: closure.completed.phase,\n message: {\n text: `GitLab issue #${context.issue?.number ?? ''} was closed; this ${closure.boardTitle} card was moved to ${closure.completed.title}.`,\n },\n } as const;\n}\n\nfunction materializeMergeRequest(context: FactoryGitLabRuleContext) {\n if (!context.mergeRequest) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:merge-request-intake`,\n board: 'review',\n source: 'gitlab-pr',\n sourceKey: context.mergeRequest.sourceKey,\n title: context.mergeRequest.title,\n url: context.mergeRequest.url,\n stage: 'intake',\n metadata: {\n gitlabHost: context.repository.host,\n gitlabProjectId: context.repository.id,\n gitlabMergeRequestIid: context.mergeRequest.number,\n ...(context.mergeRequest.createdAt ? { sourceCreatedAt: context.mergeRequest.createdAt } : {}),\n factoryAuthored: context.mergeRequest.factoryAuthored,\n authorTrusted: context.mergeRequest.authorTrusted,\n autoStartCandidate:\n ((trustedGitLabActor(context) && context.mergeRequest.authorTrusted) || context.mergeRequest.factoryAuthored) &&\n createdAfterFactory(context.mergeRequest.createdAt, context.factory.createdAt),\n state: context.mergeRequest.state,\n draft: context.mergeRequest.draft,\n merged: context.mergeRequest.merged,\n assignees: context.mergeRequest.assignees ?? [],\n requestedReviewers: context.mergeRequest.reviewers ?? [],\n labels: context.mergeRequest.labels ?? [],\n labelColors: context.mergeRequest.labelColors ?? {},\n headBranch: context.mergeRequest.headBranch,\n baseBranch: context.mergeRequest.baseBranch,\n ...(context.mergeRequest.author ? { author: context.mergeRequest.author } : {}),\n },\n } as const;\n}\n\nfunction mergeRequestUpdated(context: FactoryGitLabRuleContext) {\n if (!context.item || context.item.source !== 'gitlab-pr' || context.board !== 'review') return;\n if (!context.mergeRequest || context.mergeRequest.state !== 'open' || context.mergeRequest.merged) return;\n if (context.item.stages.some(stage => stage === 'intake')) return;\n // An untrusted push must not start review passes on demand.\n if (!trustedGitLabActor(context) && !context.mergeRequest.factoryAuthored) return;\n const alreadyReviewing = context.item.stages.some(stage => stage === 'review');\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-updated`,\n board: 'review',\n stage: 'review',\n ...(alreadyReviewing ? { reenter: true } : {}),\n } as const;\n}\n\nfunction mergeRequestCommented(context: FactoryGitLabRuleContext) {\n if (!context.item || !context.mergeRequest || !context.note || context.board !== 'work') return;\n if (context.mergeRequest.state !== 'open' || context.mergeRequest.merged) return;\n if (context.actor.type === 'gitlab' && context.actor.factoryAuthored) return;\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-merge-request-comment`,\n role: 'work',\n priority: 'high',\n message:\n `${context.note.author ?? 'Someone'} commented on GitLab merge request !${context.mergeRequest.number} ` +\n `(${context.note.url ?? context.mergeRequest.url}). Read the comment, address it if you agree, and push ` +\n 'the fixes to the merge-request branch. Reply in GitLab to anything you are deliberately not changing.',\n } as const;\n}\n\nfunction mergeRequestMerged(context: FactoryGitLabRuleContext) {\n if (!context.item || !context.mergeRequest?.merged) return;\n if (context.board === 'review') {\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:merge-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text: `GitLab merge request !${context.mergeRequest.number} merged; this Review card was moved to Done.`,\n },\n } as const;\n }\n if (context.board !== 'work') return;\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `GitLab merge request !${context.mergeRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this merge request merged.',\n } as const;\n}\n\nfunction mergeRequestClosed(context: FactoryGitLabRuleContext) {\n if (!context.item || !context.mergeRequest || context.mergeRequest.merged || context.board !== 'review') return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:merge-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `GitLab merge request !${context.mergeRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nexport const defaultGitLabRules = Object.freeze({\n issueOpened,\n issueUpdated: retriageIssue,\n issueClosed,\n issueCommented: retriageIssue,\n mergeRequestOpened: materializeMergeRequest,\n mergeRequestUpdated,\n mergeRequestCommented,\n mergeRequestMerged,\n mergeRequestClosed,\n} satisfies GitLabEventRules);\n\nexport function resolveGitLabRules(overrides?: GitLabRuleOverrides): GitLabEventRules {\n if (overrides !== undefined && (overrides === null || typeof overrides !== 'object' || Array.isArray(overrides))) {\n throw new Error('GitLab rules must be an object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryGitLabRuleContext> | null> = { ...defaultGitLabRules };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultGitLabRules, key)) {\n throw new Error(`Unknown GitLab rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryGitLabEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`GitLab rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return recordExplicitRuleOverrides(Object.freeze(rules) as GitLabEventRules, overrides);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAQ3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,8CAA8C,CAAC;AAEtD,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,KAAK,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAiHtE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,8BAA8B,CAAC,CAAC,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3F,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACpH,yBAAyB,CACvB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,GAAG,eAAe,EAC/B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAChC;AAED,UAAU,kBAAkB;IAC1B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,aAAa,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB;AAOD,qBAAa,WAAW;;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,kBAAkB;IAElD,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,CAAA;KAAE,CAAC;CAoe9E;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,kBAAkB,GAC1B,CAAC,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAYhE"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/rules.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAS3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,8CAA8C,CAAC;AAEtD,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,KAAK,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAiHtE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,8BAA8B,CAAC,CAAC,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3F,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACpH,yBAAyB,CACvB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,GAAG,eAAe,EAC/B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAChC;AAED,UAAU,kBAAkB;IAC1B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,aAAa,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB;AAOD,qBAAa,WAAW;;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,kBAAkB;IAElD,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,CAAA;KAAE,CAAC;CA6hB9E;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,kBAAkB,GAC1B,CAAC,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAYhE"}
@@ -1,7 +1,9 @@
1
1
  import { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from "../../rules/validation.js";
2
- import { boardForWorkItem } from "../../boards/semantics.js";
2
+ import { boardEventHandlers, boardForWorkItem, eventBoardId, rulePhaseField, withBoardClosure } from "../../boards/semantics.js";
3
3
  import "../../boards/index.js";
4
4
  import { WorkItemUpdateConflictError } from "../../storage/domains/work-items/base.js";
5
+ import { hasExplicitRuleOverride, selectEventRule } from "../../rules/event-rules.js";
6
+ import { recordPullRequestHead } from "../pull-request-head.js";
5
7
  import { encodeIssueReference, encodeSourceId } from "./integration.js";
6
8
  //#region src/integrations/gitlab/rules.ts
7
9
  const RULE_TIMEOUT_MS = 5e3;
@@ -215,7 +217,7 @@ var GitLabRules = class {
215
217
  trusted: actorTrusted,
216
218
  factoryAuthored: false
217
219
  };
218
- const candidates = event === "mergeRequestMerged" ? [...new Map([reviewItem, authoringItem].filter(Boolean).map((item) => [item.id, item])).values()] : event === "mergeRequestCommented" ? [authoringItem ?? reviewItem] : event === "mergeRequestOpened" ? [authoringItem] : event.startsWith("mergeRequest") ? [reviewItem ?? authoringItem] : [issueItem];
220
+ const candidates = event === "mergeRequestMerged" ? [...new Map([reviewItem, authoringItem].filter(Boolean).map((item) => [item.id, item])).values()] : event === "mergeRequestCommented" ? [authoringItem ?? reviewItem] : event === "mergeRequestOpened" ? [authoringItem, ...authoringItem ? [authoringItem] : []] : event.startsWith("mergeRequest") ? [reviewItem ?? authoringItem] : [issueItem];
219
221
  const results = [];
220
222
  for (const [index, item] of candidates.entries()) results.push(await this.#evaluate({
221
223
  parsed,
@@ -243,7 +245,8 @@ var GitLabRules = class {
243
245
  mergeRequestKey,
244
246
  factoryAuthored: Boolean(authoringItem),
245
247
  item,
246
- ingressIdentity: `gitlab:${projectId}:${parsed.deliveryId}${index ? `:${item?.id ?? index}` : ""}`
248
+ ingressIdentity: `gitlab:${projectId}:${parsed.deliveryId}${index ? `:${item?.id ?? index}` : ""}`,
249
+ ...event === "mergeRequestOpened" ? { mergeRequestOpenedScope: index ? "board" : "intake" } : {}
247
250
  }));
248
251
  const branchOwners = headBranch ? items.filter((item) => item.externalSource?.type !== "pull-request" && Object.values(item.sessions).some((session) => session.branch === headBranch)) : [];
249
252
  if (authoringItem && mergeRequestIid && branchOwners.length === 1) await this.#trackOpenMergeRequest({
@@ -252,8 +255,27 @@ var GitLabRules = class {
252
255
  event,
253
256
  mergeRequestIid,
254
257
  eventAt: timestamp(mergeRequest?.updated_at),
255
- opened: string(mergeRequest?.state) === "opened" && (actorTrusted || mergeRequestAuthorTrusted)
258
+ opened: string(mergeRequest?.state) === "opened" && (actorTrusted || mergeRequestAuthorTrusted),
259
+ headSha: string(object(mergeRequest?.last_commit)?.id)?.toLowerCase()
256
260
  });
261
+ const headSha = string(object(mergeRequest?.last_commit)?.id)?.toLowerCase();
262
+ const headAt = timestamp(mergeRequest?.updated_at);
263
+ if (event === "mergeRequestUpdated" && headSha && reviewItem) {
264
+ const parent = items.find((item) => item.id === reviewItem.parentWorkItemId);
265
+ await recordPullRequestHead(this.options.storage, {
266
+ orgId: reviewItem.orgId,
267
+ id: reviewItem.id,
268
+ headSha,
269
+ headAt
270
+ });
271
+ if (parent) await recordPullRequestHead(this.options.storage, {
272
+ orgId: parent.orgId,
273
+ id: parent.id,
274
+ headSha,
275
+ headAt,
276
+ ownedPullRequestNumber: mergeRequestIid
277
+ });
278
+ }
257
279
  if (results.some((result) => result.status === "committed")) return { status: "committed" };
258
280
  if (results.some((result) => result.status === "replayed")) return { status: "replayed" };
259
281
  return results[0] ?? { status: "ignored" };
@@ -288,8 +310,13 @@ var GitLabRules = class {
288
310
  open: !settling
289
311
  }
290
312
  } };
291
- if (!settling) patch.openPullRequestNumber = input.mergeRequestIid;
292
- else if (recorded === input.mergeRequestIid) {
313
+ if (!settling) {
314
+ if (recorded !== input.mergeRequestIid) {
315
+ patch.pullRequestHeadSha = input.headSha ?? null;
316
+ patch.pullRequestHeadAt = input.eventAt ?? null;
317
+ }
318
+ patch.openPullRequestNumber = input.mergeRequestIid;
319
+ } else if (recorded === input.mergeRequestIid) {
293
320
  patch.openPullRequestNumber = null;
294
321
  if (input.event === "mergeRequestMerged" && typeof item.metadata?.reviewVerdict === "string") patch.reviewVerdict = null;
295
322
  }
@@ -345,7 +372,9 @@ var GitLabRules = class {
345
372
  metadata: input.item.metadata
346
373
  },
347
374
  board: boardForWorkItem(input.item),
348
- itemRevision: input.item.revision
375
+ itemRevision: input.item.revision,
376
+ ...withBoardClosure(this.options.boards, input.item),
377
+ ...rulePhaseField(this.options.boards, input.item)
349
378
  } : {},
350
379
  intake: input.board,
351
380
  event: input.event,
@@ -398,7 +427,9 @@ var GitLabRules = class {
398
427
  baseBranch: string(input.mergeRequest?.target_branch) ?? ""
399
428
  } } : {}
400
429
  };
401
- const rule = this.options.gitlab.rules[input.event];
430
+ const boardRules = boardEventHandlers(this.options.boards, input.mergeRequestOpenedScope === "intake" ? void 0 : eventBoardId(context), "gitlab");
431
+ const rule = input.mergeRequestOpenedScope === "board" ? hasExplicitRuleOverride(this.options.gitlab.rules, input.event) ? void 0 : boardRules?.mergeRequestOpened : selectEventRule(this.options.gitlab.rules, boardRules, input.event);
432
+ if (input.mergeRequestOpenedScope === "board" && !rule) return { status: "ignored" };
402
433
  let decision;
403
434
  let decisions = [];
404
435
  let outcome = { status: "accepted" };
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","names":["#targets","#ingestProject","#trusted","#evaluate","#trackOpenMergeRequest"],"sources":["../../../src/integrations/gitlab/rules.ts"],"sourcesContent":["import { boardForWorkItem } from '../../boards/index.js';\nimport type { BoardRegistry } from '../../boards/index.js';\nimport type {\n FactoryGitLabEventName,\n FactoryGitLabRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n} from '../../rules/types.js';\nimport { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from '../../rules/validation.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type {\n ExternalRepositoryProjectTarget,\n SourceControlStorageHandle,\n} from '../../storage/domains/source-control/base.js';\nimport { WorkItemUpdateConflictError } from '../../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport type { GitLabEventRules } from './default-rules.js';\nimport { encodeIssueReference, encodeSourceId, GITLAB_TRUSTED_ACCESS_LEVEL } from './integration.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\n\ntype IngressStatus = 'ignored' | 'committed' | 'replayed' | 'missing';\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction string(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** GitLab sends `2024-01-02T03:04:05Z` or `2024-01-02 03:04:05 UTC`. */\nfunction timestamp(value: unknown): number | undefined {\n const text = string(value);\n if (!text) return undefined;\n const parsed = Date.parse(text.replace(' UTC', 'Z').replace(' ', 'T'));\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction boolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction usernames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n const username = string(object(entry)?.username);\n return username ? [username] : [];\n });\n}\n\nfunction labelNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n if (typeof entry === 'string') return entry ? [entry] : [];\n const title = string(object(entry)?.title) ?? string(object(entry)?.name);\n return title ? [title] : [];\n });\n}\n\nfunction labelColors(value: unknown): Record<string, string> {\n if (!Array.isArray(value)) return {};\n return Object.fromEntries(\n value.flatMap(entry => {\n const label = object(entry);\n const name = string(label?.title) ?? string(label?.name);\n const color = string(label?.color);\n return name && color ? [[name, color]] : [];\n }),\n );\n}\n\nfunction eventName(parsed: ParsedGitLabWebhook): FactoryGitLabEventName | undefined {\n const attributes = object(parsed.payload.object_attributes);\n const action = string(attributes?.action)?.toLowerCase();\n if (parsed.event === 'Issue Hook') {\n if (action === 'open' || action === 'reopen') return 'issueOpened';\n if (action === 'close') return 'issueClosed';\n return 'issueUpdated';\n }\n if (parsed.event === 'Note Hook') {\n const noteableType = string(attributes?.noteable_type);\n if (noteableType === 'Issue') return 'issueCommented';\n if (noteableType === 'MergeRequest') return 'mergeRequestCommented';\n return;\n }\n if (parsed.event !== 'Merge Request Hook') return;\n if (action === 'open' || action === 'reopen') return 'mergeRequestOpened';\n if (action === 'merge') return 'mergeRequestMerged';\n if (action === 'close') return 'mergeRequestClosed';\n return 'mergeRequestUpdated';\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction hostFromPayload(parsed: ParsedGitLabWebhook): string | undefined {\n const declaredHost = parsed.instanceHost ? normalizeHost(parsed.instanceHost) : undefined;\n const projectUrl = string(object(parsed.payload.project)?.web_url);\n if (!projectUrl) return declaredHost;\n try {\n const payloadHost = normalizeHost(new URL(projectUrl).host);\n if (declaredHost && declaredHost !== payloadHost) return undefined;\n return payloadHost;\n } catch {\n return undefined;\n }\n}\n\nfunction mergeRequestSourceKey(host: string, projectId: number, mergeRequestIid: number): string {\n return `gitlab-pr:${Buffer.from(JSON.stringify({ version: 1, host, projectId, mergeRequestIid }), 'utf8').toString('base64url')}`;\n}\n\nasync function withRuleTimeout<T>(promise: Promise<T>): Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nconst MAX_STALE_RULE_ATTEMPTS = 3;\n\nexport interface GitLabRulesIntegration {\n readonly rules: GitLabEventRules;\n resolveActiveConnectionForHost?(storedConnectionId: string, host: string): Promise<string>;\n getProjectMemberAccessLevel(connectionId: string, projectId: string, username: string): Promise<number | undefined>;\n getWorkItemAuthorUsername(\n connectionId: string,\n projectId: string,\n kind: 'issue' | 'merge_request',\n iid: number,\n ): Promise<string | undefined>;\n}\n\ninterface GitLabRulesOptions {\n gitlab: GitLabRulesIntegration;\n sourceControl: SourceControlStorageHandle;\n projects: FactoryProjectsStorage;\n storage: WorkItemsStorage;\n intake: IntakeStorage;\n configVersion: string;\n boards: BoardRegistry;\n}\n\ninterface ProjectTarget {\n target: ExternalRepositoryProjectTarget;\n connectionIds: string[];\n}\n\nexport class GitLabRules {\n constructor(private readonly options: GitLabRulesOptions) {}\n\n async ingest(parsed: ParsedGitLabWebhook): Promise<{ status: IngressStatus }> {\n const event = eventName(parsed);\n const project = object(parsed.payload.project);\n const projectId = number(project?.id);\n const projectPath = string(project?.path_with_namespace);\n const host = hostFromPayload(parsed);\n const username = string(parsed.payload.user_username) ?? string(object(parsed.payload.user)?.username);\n if (!event || !projectId || !projectPath || !host || !username) return { status: 'ignored' };\n\n const targets = await this.#targets(String(projectId), host);\n if (targets.length === 0) return { status: 'ignored' };\n const results: Array<{ status: IngressStatus }> = [];\n for (const target of targets) {\n results.push(await this.#ingestProject(parsed, event, projectId, projectPath, host, username, target));\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n async #targets(repositoryExternalId: string, host: string): Promise<ProjectTarget[]> {\n const grouped = new Map<string, ProjectTarget>();\n const keys = (await this.options.sourceControl.projectRepositories.listConfiguredExternalKeys()).filter(\n key => key.repositoryExternalId === repositoryExternalId,\n );\n for (const key of keys) {\n const targets = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: key.installationExternalId,\n repositoryExternalId,\n });\n for (const target of targets) {\n const installation = await this.options.sourceControl.installations.findByExternalId({\n orgId: target.orgId,\n externalId: key.installationExternalId,\n });\n const installationHost = string(installation?.providerMetadata.host);\n if (!installationHost || normalizeHost(installationHost) !== normalizeHost(host)) continue;\n const id = `${target.orgId}:${target.factoryProjectId}`;\n const current = grouped.get(id);\n if (current) {\n if (!current.connectionIds.includes(key.installationExternalId)) {\n current.connectionIds.push(key.installationExternalId);\n }\n } else {\n grouped.set(id, { target, connectionIds: [key.installationExternalId] });\n }\n }\n }\n return [...grouped.values()];\n }\n\n async #ingestProject(\n parsed: ParsedGitLabWebhook,\n event: FactoryGitLabEventName,\n projectId: number,\n projectPath: string,\n host: string,\n username: string,\n target: ProjectTarget,\n ): Promise<{ status: IngressStatus }> {\n const factoryProject = await this.options.projects.get({\n orgId: target.target.orgId,\n id: target.target.factoryProjectId,\n });\n if (!factoryProject) return { status: 'missing' };\n\n const sourceId = encodeSourceId({ host, projectId: String(projectId) });\n const config = await this.options.intake.getConfig({\n orgId: target.target.orgId,\n integrationIds: ['gitlab'],\n });\n if (!config.gitlab?.enabled || !config.gitlab.sourceIds?.includes(sourceId)) return { status: 'ignored' };\n const binding = (\n await this.options.intake.listBindings({ orgId: target.target.orgId, integrationId: 'gitlab' })\n ).find(\n candidate => candidate.sourceId === sourceId && candidate.factoryProjectId === target.target.factoryProjectId,\n );\n if (!binding) return { status: 'ignored' };\n const board = this.options.boards.get(binding.board ?? 'work');\n if (!board) return { status: 'ignored' };\n\n const connectionIds = [\n ...new Set(\n await Promise.all(\n target.connectionIds.map(\n connectionId => this.options.gitlab.resolveActiveConnectionForHost?.(connectionId, host) ?? connectionId,\n ),\n ),\n ),\n ];\n\n const attributes = object(parsed.payload.object_attributes);\n const issue = parsed.event === 'Note Hook' ? object(parsed.payload.issue) : attributes;\n const mergeRequest = parsed.event === 'Note Hook' ? object(parsed.payload.merge_request) : attributes;\n const issueIid = number(issue?.iid);\n const mergeRequestIid = number(mergeRequest?.iid);\n const issueSourceKey = issueIid\n ? encodeIssueReference({ host, projectId: String(projectId), issueIid })\n : undefined;\n const mergeRequestKey = mergeRequestIid ? mergeRequestSourceKey(host, projectId, mergeRequestIid) : undefined;\n const items = await this.options.storage.list({\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n });\n const issueItem = issueSourceKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === issueSourceKey,\n )\n : undefined;\n const reviewItem = mergeRequestKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === mergeRequestKey,\n )\n : undefined;\n const headBranch = string(mergeRequest?.source_branch) ?? '';\n const authoringItem = headBranch\n ? items.find(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : undefined;\n const actorTrusted = await this.#trusted(connectionIds, String(projectId), username);\n const eventUserId = number(object(parsed.payload.user)?.id) ?? number(parsed.payload.user_id);\n const resolveAuthor = async (\n item: Record<string, unknown> | undefined,\n kind: 'issue' | 'merge_request',\n iid: number | undefined,\n ): Promise<string | undefined> => {\n if (!item || !iid) return undefined;\n const embedded = string(object(item.author)?.username);\n if (embedded) return embedded;\n if (eventUserId && eventUserId === number(item.author_id)) return username;\n for (const connectionId of connectionIds) {\n try {\n const resolved = await this.options.gitlab.getWorkItemAuthorUsername(\n connectionId,\n String(projectId),\n kind,\n iid,\n );\n if (resolved) return resolved;\n } catch {\n // Another active credential may cover this project; otherwise leave author trust unset.\n }\n }\n return undefined;\n };\n const [issueAuthor, mergeRequestAuthor] = await Promise.all([\n resolveAuthor(issue, 'issue', issueIid),\n resolveAuthor(mergeRequest, 'merge_request', mergeRequestIid),\n ]);\n const authorTrusted = async (author: string | undefined): Promise<boolean> => {\n if (!author) return false;\n return author === username ? actorTrusted : this.#trusted(connectionIds, String(projectId), author);\n };\n const [issueAuthorTrusted, mergeRequestAuthorTrusted] = await Promise.all([\n authorTrusted(issueAuthor),\n authorTrusted(mergeRequestAuthor),\n ]);\n const actor: FactoryRuleActor = {\n type: 'gitlab',\n username,\n trusted: actorTrusted,\n factoryAuthored: false,\n };\n\n const candidates: Array<WorkItemRow | undefined> =\n event === 'mergeRequestMerged'\n ? [...new Map([reviewItem, authoringItem].filter(Boolean).map(item => [item!.id, item!])).values()]\n : event === 'mergeRequestCommented'\n ? [authoringItem ?? reviewItem]\n : event === 'mergeRequestOpened'\n ? [authoringItem]\n : event.startsWith('mergeRequest')\n ? [reviewItem ?? authoringItem]\n : [issueItem];\n const results = [];\n for (const [index, item] of candidates.entries()) {\n results.push(\n await this.#evaluate({\n parsed,\n event,\n projectId,\n projectPath,\n host,\n actor,\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n factoryProject,\n board: { board: board.id, initialPhase: board.initialPhase },\n issue,\n issueAuthor,\n issueAuthorTrusted,\n issueIid,\n issueSourceKey,\n mergeRequest,\n mergeRequestAuthor,\n mergeRequestAuthorTrusted,\n mergeRequestIid,\n mergeRequestKey,\n factoryAuthored: Boolean(authoringItem),\n item,\n ingressIdentity: `gitlab:${projectId}:${parsed.deliveryId}${index ? `:${item?.id ?? index}` : ''}`,\n }),\n );\n }\n // The authoring Work item records which merge request it has out, so the\n // Work transition policy keeps an agent from closing the work while it is\n // open. Sessions carry no repository, so a branch shared by two Work cards\n // is ambiguous and records nothing.\n const branchOwners = headBranch\n ? items.filter(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : [];\n if (authoringItem && mergeRequestIid && branchOwners.length === 1) {\n await this.#trackOpenMergeRequest({\n orgId: authoringItem.orgId,\n id: authoringItem.id,\n event,\n mergeRequestIid,\n eventAt: timestamp(mergeRequest?.updated_at),\n opened: string(mergeRequest?.state) === 'opened' && (actorTrusted || mergeRequestAuthorTrusted),\n });\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n /**\n * Records or clears the merge request a Work card has out. Each merge\n * request's last applied event time is kept on the card, so an opening that\n * is older than a close already applied (a replay, or a close delivered\n * first) is ignored, while a genuine reopen is newer and records it again.\n */\n async #trackOpenMergeRequest(input: {\n orgId: string;\n id: string;\n event: string;\n mergeRequestIid: number;\n eventAt: number | undefined;\n opened: boolean;\n }): Promise<void> {\n const settling = input.event === 'mergeRequestMerged' || input.event === 'mergeRequestClosed';\n if (input.event !== 'mergeRequestOpened' && !settling) return;\n const key = String(input.mergeRequestIid);\n for (let attempt = 0; attempt < 3; attempt++) {\n const item = await this.options.storage.get({ orgId: input.orgId, id: input.id });\n if (!item) return;\n const events = object(item.metadata?.mergeRequestEvents) ?? {};\n const last = object(events[key]);\n const lastAt = number(last?.at);\n if (input.eventAt !== undefined && lastAt !== undefined && lastAt >= input.eventAt) return;\n // Without a timestamp, only an opening that no close has settled is trusted.\n if (input.eventAt === undefined && !settling && last?.open === false) return;\n if (!settling && !input.opened) return;\n const recorded = item.metadata?.openPullRequestNumber;\n const patch: Record<string, unknown> = {\n mergeRequestEvents: { ...events, [key]: { at: input.eventAt ?? lastAt ?? null, open: !settling } },\n };\n if (!settling) patch.openPullRequestNumber = input.mergeRequestIid;\n else if (recorded === input.mergeRequestIid) {\n patch.openPullRequestNumber = null;\n if (input.event === 'mergeRequestMerged' && typeof item.metadata?.reviewVerdict === 'string') {\n patch.reviewVerdict = null;\n }\n }\n try {\n await this.options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n expectedRevision: item.revision,\n patch: { metadata: patch },\n });\n return;\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n }\n }\n }\n\n async #trusted(connectionIds: string[], projectId: string, username: string): Promise<boolean> {\n for (const connectionId of connectionIds) {\n try {\n const level = await this.options.gitlab.getProjectMemberAccessLevel(connectionId, projectId, username);\n if (level !== undefined) return level >= GITLAB_TRUSTED_ACCESS_LEVEL;\n } catch {\n // Try another active credential covering the same canonical project; otherwise fail closed.\n }\n }\n return false;\n }\n\n async #evaluate(\n input: {\n parsed: ParsedGitLabWebhook;\n event: FactoryGitLabEventName;\n projectId: number;\n projectPath: string;\n host: string;\n actor: FactoryRuleActor;\n orgId: string;\n factoryProjectId: string;\n factoryProject: { createdAt: Date };\n board: { board: string; initialPhase: string };\n issue: Record<string, unknown> | undefined;\n issueAuthor: string | undefined;\n issueAuthorTrusted: boolean;\n issueIid: number | undefined;\n issueSourceKey: string | undefined;\n mergeRequest: Record<string, unknown> | undefined;\n mergeRequestAuthor: string | undefined;\n mergeRequestAuthorTrusted: boolean;\n mergeRequestIid: number | undefined;\n mergeRequestKey: string | undefined;\n factoryAuthored: boolean;\n item: WorkItemRow | undefined;\n ingressIdentity: string;\n },\n attempt = 1,\n ): Promise<{ status: IngressStatus }> {\n const note = object(input.parsed.payload.object_attributes);\n const issueAuthor = input.issueAuthor;\n const mergeRequestAuthor = input.mergeRequestAuthor;\n const mergeRequestState = string(input.mergeRequest?.state);\n const context: FactoryGitLabRuleContext = {\n tenant: { orgId: input.orgId, projectId: input.factoryProjectId },\n actor: input.actor,\n ingress: { type: 'gitlab', id: input.ingressIdentity },\n cause: `gitlab.${input.event}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n ...(input.item\n ? {\n item: {\n id: input.item.id,\n source:\n input.item.externalSource?.type === 'pull-request'\n ? ('gitlab-pr' as const)\n : input.item.externalSource?.integrationId === 'gitlab'\n ? ('gitlab-issue' as const)\n : ('manual' as const),\n sourceKey: input.item.externalSource?.externalId ?? null,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n url: input.item.externalSource?.url ?? null,\n stages: input.item.stages,\n acceptedAt: input.item.acceptedAt,\n metadata: input.item.metadata,\n },\n board: boardForWorkItem(input.item),\n itemRevision: input.item.revision,\n }\n : {}),\n intake: input.board,\n event: input.event,\n deliveryId: input.parsed.deliveryId,\n factory: { createdAt: input.factoryProject.createdAt.toISOString() },\n repository: { id: input.projectId, fullName: input.projectPath, host: input.host },\n ...(input.issueIid && input.issueSourceKey && string(input.issue?.title)\n ? {\n issue: {\n number: input.issueIid,\n sourceKey: input.issueSourceKey,\n title: string(input.issue?.title)!,\n url:\n string(input.issue?.url) ??\n string(input.issue?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/issues/${input.issueIid}`,\n ...(string(input.issue?.created_at) ? { createdAt: string(input.issue?.created_at) } : {}),\n ...(string(input.issue?.updated_at) ? { updatedAt: string(input.issue?.updated_at) } : {}),\n assignees: usernames(input.parsed.payload.assignees ?? input.issue?.assignees),\n labels: labelNames(input.issue?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.issue?.labels ?? input.parsed.payload.labels),\n state: string(input.issue?.state) === 'closed' ? ('closed' as const) : ('open' as const),\n ...(issueAuthor ? { author: issueAuthor } : {}),\n authorTrusted: input.issueAuthorTrusted,\n },\n }\n : {}),\n ...(input.parsed.event === 'Note Hook' && number(note?.id)\n ? {\n note: {\n id: number(note?.id)!,\n ...(string(note?.note) ? { body: string(note?.note) } : {}),\n ...(string(note?.url) ? { url: string(note?.url) } : {}),\n ...(string(object(input.parsed.payload.user)?.username)\n ? { author: string(object(input.parsed.payload.user)?.username) }\n : {}),\n ...(string(note?.created_at) ? { createdAt: string(note?.created_at) } : {}),\n ...(string(note?.updated_at) ? { updatedAt: string(note?.updated_at) } : {}),\n },\n }\n : {}),\n ...(input.mergeRequestIid && input.mergeRequestKey && string(input.mergeRequest?.title)\n ? {\n mergeRequest: {\n number: input.mergeRequestIid,\n sourceKey: input.mergeRequestKey,\n title: string(input.mergeRequest?.title)!,\n url:\n string(input.mergeRequest?.url) ??\n string(input.mergeRequest?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/merge_requests/${input.mergeRequestIid}`,\n ...(string(input.mergeRequest?.created_at) ? { createdAt: string(input.mergeRequest?.created_at) } : {}),\n state:\n mergeRequestState === 'closed' || mergeRequestState === 'merged'\n ? ('closed' as const)\n : ('open' as const),\n draft: boolean(input.mergeRequest?.draft) ?? boolean(input.mergeRequest?.work_in_progress) ?? false,\n merged: input.event === 'mergeRequestMerged' || mergeRequestState === 'merged',\n assignees: usernames(input.parsed.payload.assignees ?? input.mergeRequest?.assignees),\n reviewers: usernames(input.parsed.payload.reviewers ?? input.mergeRequest?.reviewers),\n labels: labelNames(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n ...(mergeRequestAuthor ? { author: mergeRequestAuthor } : {}),\n authorTrusted: input.mergeRequestAuthorTrusted,\n factoryAuthored: input.factoryAuthored,\n headBranch: string(input.mergeRequest?.source_branch) ?? '',\n baseBranch: string(input.mergeRequest?.target_branch) ?? '',\n },\n }\n : {}),\n };\n const rule = this.options.gitlab.rules[input.event];\n let decision: FactoryRuleDecision | void;\n let decisions: Record<string, unknown>[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : undefined;\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n decisions = validateFactoryRuleDecisions([decision]).map(entry => {\n assertFactoryDecisionTarget(\n entry,\n this.options.boards,\n input.item ? boardForWorkItem(input.item) : undefined,\n );\n return { ...entry };\n });\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 GitLab rule failed.',\n };\n }\n\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: input.orgId,\n factoryProjectId: input.factoryProjectId,\n workItemId: input.item?.id ?? null,\n ingress: { identity: input.ingressIdentity, triggerType: `gitlab.${input.event}` },\n configVersion: this.options.configVersion,\n expectedRevision: input.item?.revision ?? null,\n actor: { ...input.actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n if (committed.status === 'stale') {\n // Nothing was persisted: re-read the item and evaluate again at its fresh revision.\n if (attempt >= MAX_STALE_RULE_ATTEMPTS) {\n throw new Error(`Factory GitLab rule evaluation for ${input.ingressIdentity} kept losing revision races.`);\n }\n const fresh = input.item ? await this.options.storage.get({ orgId: input.orgId, id: input.item.id }) : null;\n if (input.item && !fresh) return { status: 'missing' };\n return this.#evaluate({ ...input, item: fresh ?? undefined }, attempt + 1);\n }\n return { status: committed.status };\n }\n}\n\nexport function attachGitLabRules(\n gitlab: GitLabRulesIntegration,\n context: IntegrationContext,\n): ((event: ParsedGitLabWebhook) => Promise<unknown>) | undefined {\n if (!context.runtime) return undefined;\n const rules = new GitLabRules({\n gitlab,\n sourceControl: context.storage.sourceControl,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n intake: context.storage.intake,\n configVersion: context.runtime.configVersion,\n boards: context.runtime.boards,\n });\n return event => rules.ingest(event);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rules.js","names":["#targets","#ingestProject","#trusted","#evaluate","#trackOpenMergeRequest"],"sources":["../../../src/integrations/gitlab/rules.ts"],"sourcesContent":["import {\n boardEventHandlers,\n boardForWorkItem,\n eventBoardId,\n rulePhaseField,\n withBoardClosure,\n} from '../../boards/index.js';\nimport type { BoardRegistry } from '../../boards/index.js';\nimport { hasExplicitRuleOverride, selectEventRule } from '../../rules/event-rules.js';\nimport type {\n FactoryGitLabEventName,\n FactoryGitLabRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n} from '../../rules/types.js';\nimport { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from '../../rules/validation.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type {\n ExternalRepositoryProjectTarget,\n SourceControlStorageHandle,\n} from '../../storage/domains/source-control/base.js';\nimport { WorkItemUpdateConflictError } from '../../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport { recordPullRequestHead } from '../pull-request-head.js';\nimport type { GitLabEventRules } from './default-rules.js';\nimport { encodeIssueReference, encodeSourceId, GITLAB_TRUSTED_ACCESS_LEVEL } from './integration.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\n\ntype IngressStatus = 'ignored' | 'committed' | 'replayed' | 'missing';\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction string(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** GitLab sends `2024-01-02T03:04:05Z` or `2024-01-02 03:04:05 UTC`. */\nfunction timestamp(value: unknown): number | undefined {\n const text = string(value);\n if (!text) return undefined;\n const parsed = Date.parse(text.replace(' UTC', 'Z').replace(' ', 'T'));\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction boolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction usernames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n const username = string(object(entry)?.username);\n return username ? [username] : [];\n });\n}\n\nfunction labelNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n if (typeof entry === 'string') return entry ? [entry] : [];\n const title = string(object(entry)?.title) ?? string(object(entry)?.name);\n return title ? [title] : [];\n });\n}\n\nfunction labelColors(value: unknown): Record<string, string> {\n if (!Array.isArray(value)) return {};\n return Object.fromEntries(\n value.flatMap(entry => {\n const label = object(entry);\n const name = string(label?.title) ?? string(label?.name);\n const color = string(label?.color);\n return name && color ? [[name, color]] : [];\n }),\n );\n}\n\nfunction eventName(parsed: ParsedGitLabWebhook): FactoryGitLabEventName | undefined {\n const attributes = object(parsed.payload.object_attributes);\n const action = string(attributes?.action)?.toLowerCase();\n if (parsed.event === 'Issue Hook') {\n if (action === 'open' || action === 'reopen') return 'issueOpened';\n if (action === 'close') return 'issueClosed';\n return 'issueUpdated';\n }\n if (parsed.event === 'Note Hook') {\n const noteableType = string(attributes?.noteable_type);\n if (noteableType === 'Issue') return 'issueCommented';\n if (noteableType === 'MergeRequest') return 'mergeRequestCommented';\n return;\n }\n if (parsed.event !== 'Merge Request Hook') return;\n if (action === 'open' || action === 'reopen') return 'mergeRequestOpened';\n if (action === 'merge') return 'mergeRequestMerged';\n if (action === 'close') return 'mergeRequestClosed';\n return 'mergeRequestUpdated';\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction hostFromPayload(parsed: ParsedGitLabWebhook): string | undefined {\n const declaredHost = parsed.instanceHost ? normalizeHost(parsed.instanceHost) : undefined;\n const projectUrl = string(object(parsed.payload.project)?.web_url);\n if (!projectUrl) return declaredHost;\n try {\n const payloadHost = normalizeHost(new URL(projectUrl).host);\n if (declaredHost && declaredHost !== payloadHost) return undefined;\n return payloadHost;\n } catch {\n return undefined;\n }\n}\n\nfunction mergeRequestSourceKey(host: string, projectId: number, mergeRequestIid: number): string {\n return `gitlab-pr:${Buffer.from(JSON.stringify({ version: 1, host, projectId, mergeRequestIid }), 'utf8').toString('base64url')}`;\n}\n\nasync function withRuleTimeout<T>(promise: Promise<T>): Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nconst MAX_STALE_RULE_ATTEMPTS = 3;\n\nexport interface GitLabRulesIntegration {\n readonly rules: GitLabEventRules;\n resolveActiveConnectionForHost?(storedConnectionId: string, host: string): Promise<string>;\n getProjectMemberAccessLevel(connectionId: string, projectId: string, username: string): Promise<number | undefined>;\n getWorkItemAuthorUsername(\n connectionId: string,\n projectId: string,\n kind: 'issue' | 'merge_request',\n iid: number,\n ): Promise<string | undefined>;\n}\n\ninterface GitLabRulesOptions {\n gitlab: GitLabRulesIntegration;\n sourceControl: SourceControlStorageHandle;\n projects: FactoryProjectsStorage;\n storage: WorkItemsStorage;\n intake: IntakeStorage;\n configVersion: string;\n boards: BoardRegistry;\n}\n\ninterface ProjectTarget {\n target: ExternalRepositoryProjectTarget;\n connectionIds: string[];\n}\n\nexport class GitLabRules {\n constructor(private readonly options: GitLabRulesOptions) {}\n\n async ingest(parsed: ParsedGitLabWebhook): Promise<{ status: IngressStatus }> {\n const event = eventName(parsed);\n const project = object(parsed.payload.project);\n const projectId = number(project?.id);\n const projectPath = string(project?.path_with_namespace);\n const host = hostFromPayload(parsed);\n const username = string(parsed.payload.user_username) ?? string(object(parsed.payload.user)?.username);\n if (!event || !projectId || !projectPath || !host || !username) return { status: 'ignored' };\n\n const targets = await this.#targets(String(projectId), host);\n if (targets.length === 0) return { status: 'ignored' };\n const results: Array<{ status: IngressStatus }> = [];\n for (const target of targets) {\n results.push(await this.#ingestProject(parsed, event, projectId, projectPath, host, username, target));\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n async #targets(repositoryExternalId: string, host: string): Promise<ProjectTarget[]> {\n const grouped = new Map<string, ProjectTarget>();\n const keys = (await this.options.sourceControl.projectRepositories.listConfiguredExternalKeys()).filter(\n key => key.repositoryExternalId === repositoryExternalId,\n );\n for (const key of keys) {\n const targets = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: key.installationExternalId,\n repositoryExternalId,\n });\n for (const target of targets) {\n const installation = await this.options.sourceControl.installations.findByExternalId({\n orgId: target.orgId,\n externalId: key.installationExternalId,\n });\n const installationHost = string(installation?.providerMetadata.host);\n if (!installationHost || normalizeHost(installationHost) !== normalizeHost(host)) continue;\n const id = `${target.orgId}:${target.factoryProjectId}`;\n const current = grouped.get(id);\n if (current) {\n if (!current.connectionIds.includes(key.installationExternalId)) {\n current.connectionIds.push(key.installationExternalId);\n }\n } else {\n grouped.set(id, { target, connectionIds: [key.installationExternalId] });\n }\n }\n }\n return [...grouped.values()];\n }\n\n async #ingestProject(\n parsed: ParsedGitLabWebhook,\n event: FactoryGitLabEventName,\n projectId: number,\n projectPath: string,\n host: string,\n username: string,\n target: ProjectTarget,\n ): Promise<{ status: IngressStatus }> {\n const factoryProject = await this.options.projects.get({\n orgId: target.target.orgId,\n id: target.target.factoryProjectId,\n });\n if (!factoryProject) return { status: 'missing' };\n\n const sourceId = encodeSourceId({ host, projectId: String(projectId) });\n const config = await this.options.intake.getConfig({\n orgId: target.target.orgId,\n integrationIds: ['gitlab'],\n });\n if (!config.gitlab?.enabled || !config.gitlab.sourceIds?.includes(sourceId)) return { status: 'ignored' };\n const binding = (\n await this.options.intake.listBindings({ orgId: target.target.orgId, integrationId: 'gitlab' })\n ).find(\n candidate => candidate.sourceId === sourceId && candidate.factoryProjectId === target.target.factoryProjectId,\n );\n if (!binding) return { status: 'ignored' };\n const board = this.options.boards.get(binding.board ?? 'work');\n if (!board) return { status: 'ignored' };\n\n const connectionIds = [\n ...new Set(\n await Promise.all(\n target.connectionIds.map(\n connectionId => this.options.gitlab.resolveActiveConnectionForHost?.(connectionId, host) ?? connectionId,\n ),\n ),\n ),\n ];\n\n const attributes = object(parsed.payload.object_attributes);\n const issue = parsed.event === 'Note Hook' ? object(parsed.payload.issue) : attributes;\n const mergeRequest = parsed.event === 'Note Hook' ? object(parsed.payload.merge_request) : attributes;\n const issueIid = number(issue?.iid);\n const mergeRequestIid = number(mergeRequest?.iid);\n const issueSourceKey = issueIid\n ? encodeIssueReference({ host, projectId: String(projectId), issueIid })\n : undefined;\n const mergeRequestKey = mergeRequestIid ? mergeRequestSourceKey(host, projectId, mergeRequestIid) : undefined;\n const items = await this.options.storage.list({\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n });\n const issueItem = issueSourceKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === issueSourceKey,\n )\n : undefined;\n const reviewItem = mergeRequestKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === mergeRequestKey,\n )\n : undefined;\n const headBranch = string(mergeRequest?.source_branch) ?? '';\n const authoringItem = headBranch\n ? items.find(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : undefined;\n const actorTrusted = await this.#trusted(connectionIds, String(projectId), username);\n const eventUserId = number(object(parsed.payload.user)?.id) ?? number(parsed.payload.user_id);\n const resolveAuthor = async (\n item: Record<string, unknown> | undefined,\n kind: 'issue' | 'merge_request',\n iid: number | undefined,\n ): Promise<string | undefined> => {\n if (!item || !iid) return undefined;\n const embedded = string(object(item.author)?.username);\n if (embedded) return embedded;\n if (eventUserId && eventUserId === number(item.author_id)) return username;\n for (const connectionId of connectionIds) {\n try {\n const resolved = await this.options.gitlab.getWorkItemAuthorUsername(\n connectionId,\n String(projectId),\n kind,\n iid,\n );\n if (resolved) return resolved;\n } catch {\n // Another active credential may cover this project; otherwise leave author trust unset.\n }\n }\n return undefined;\n };\n const [issueAuthor, mergeRequestAuthor] = await Promise.all([\n resolveAuthor(issue, 'issue', issueIid),\n resolveAuthor(mergeRequest, 'merge_request', mergeRequestIid),\n ]);\n const authorTrusted = async (author: string | undefined): Promise<boolean> => {\n if (!author) return false;\n return author === username ? actorTrusted : this.#trusted(connectionIds, String(projectId), author);\n };\n const [issueAuthorTrusted, mergeRequestAuthorTrusted] = await Promise.all([\n authorTrusted(issueAuthor),\n authorTrusted(mergeRequestAuthor),\n ]);\n const actor: FactoryRuleActor = {\n type: 'gitlab',\n username,\n trusted: actorTrusted,\n factoryAuthored: false,\n };\n\n const candidates: Array<WorkItemRow | undefined> =\n event === 'mergeRequestMerged'\n ? [...new Map([reviewItem, authoringItem].filter(Boolean).map(item => [item!.id, item!])).values()]\n : event === 'mergeRequestCommented'\n ? [authoringItem ?? reviewItem]\n : event === 'mergeRequestOpened'\n ? [authoringItem, ...(authoringItem ? [authoringItem] : [])]\n : event.startsWith('mergeRequest')\n ? [reviewItem ?? authoringItem]\n : [issueItem];\n const results = [];\n for (const [index, item] of candidates.entries()) {\n results.push(\n await this.#evaluate({\n parsed,\n event,\n projectId,\n projectPath,\n host,\n actor,\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n factoryProject,\n board: { board: board.id, initialPhase: board.initialPhase },\n issue,\n issueAuthor,\n issueAuthorTrusted,\n issueIid,\n issueSourceKey,\n mergeRequest,\n mergeRequestAuthor,\n mergeRequestAuthorTrusted,\n mergeRequestIid,\n mergeRequestKey,\n factoryAuthored: Boolean(authoringItem),\n item,\n ingressIdentity: `gitlab:${projectId}:${parsed.deliveryId}${index ? `:${item?.id ?? index}` : ''}`,\n // A merge request opening concerns two cards: its own Review card,\n // which the integration handler files against the authoring item,\n // and that authoring item, which its board may react to. The second\n // evaluation runs only the board handler, under an id-suffixed\n // identity so it is not dropped as a replay of the first.\n ...(event === 'mergeRequestOpened' ? { mergeRequestOpenedScope: index ? ('board' as const) : ('intake' as const) } : {}),\n }),\n );\n }\n // The authoring Work item records which merge request it has out, so the\n // Work transition policy keeps an agent from closing the work while it is\n // open. Sessions carry no repository, so a branch shared by two Work cards\n // is ambiguous and records nothing.\n const branchOwners = headBranch\n ? items.filter(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : [];\n if (authoringItem && mergeRequestIid && branchOwners.length === 1) {\n await this.#trackOpenMergeRequest({\n orgId: authoringItem.orgId,\n id: authoringItem.id,\n event,\n mergeRequestIid,\n eventAt: timestamp(mergeRequest?.updated_at),\n opened: string(mergeRequest?.state) === 'opened' && (actorTrusted || mergeRequestAuthorTrusted),\n headSha: string(object(mergeRequest?.last_commit)?.id)?.toLowerCase(),\n });\n }\n // A push moves the head a recorded review verdict was given on. Recording\n // the new head lets the verdict badge show that it is outdated, while the\n // verdict itself stays so the next pass still runs as a re-review.\n const headSha = string(object(mergeRequest?.last_commit)?.id)?.toLowerCase();\n const headAt = timestamp(mergeRequest?.updated_at);\n if (event === 'mergeRequestUpdated' && headSha && reviewItem) {\n const parent = items.find(item => item.id === reviewItem.parentWorkItemId);\n // The authoring Work item mirrors the verdict of the merge request it has\n // out; a push to an older one must not mark its successor's verdict.\n await recordPullRequestHead(this.options.storage, {\n orgId: reviewItem.orgId,\n id: reviewItem.id,\n headSha,\n headAt,\n });\n if (parent) {\n await recordPullRequestHead(this.options.storage, {\n orgId: parent.orgId,\n id: parent.id,\n headSha,\n headAt,\n ownedPullRequestNumber: mergeRequestIid,\n });\n }\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n /**\n * Records or clears the merge request a Work card has out. Each merge\n * request's last applied event time is kept on the card, so an opening that\n * is older than a close already applied (a replay, or a close delivered\n * first) is ignored, while a genuine reopen is newer and records it again.\n */\n async #trackOpenMergeRequest(input: {\n orgId: string;\n id: string;\n event: string;\n mergeRequestIid: number;\n eventAt: number | undefined;\n opened: boolean;\n headSha: string | undefined;\n }): Promise<void> {\n const settling = input.event === 'mergeRequestMerged' || input.event === 'mergeRequestClosed';\n if (input.event !== 'mergeRequestOpened' && !settling) return;\n const key = String(input.mergeRequestIid);\n for (let attempt = 0; attempt < 3; attempt++) {\n const item = await this.options.storage.get({ orgId: input.orgId, id: input.id });\n if (!item) return;\n const events = object(item.metadata?.mergeRequestEvents) ?? {};\n const last = object(events[key]);\n const lastAt = number(last?.at);\n if (input.eventAt !== undefined && lastAt !== undefined && lastAt >= input.eventAt) return;\n // Without a timestamp, only an opening that no close has settled is trusted.\n if (input.eventAt === undefined && !settling && last?.open === false) return;\n if (!settling && !input.opened) return;\n const recorded = item.metadata?.openPullRequestNumber;\n const patch: Record<string, unknown> = {\n mergeRequestEvents: { ...events, [key]: { at: input.eventAt ?? lastAt ?? null, open: !settling } },\n };\n if (!settling) {\n // A head recorded for an earlier merge request must not mark this one's\n // verdict outdated. A replayed opening of the merge request already\n // recorded keeps the head its pushes left.\n if (recorded !== input.mergeRequestIid) {\n patch.pullRequestHeadSha = input.headSha ?? null;\n patch.pullRequestHeadAt = input.eventAt ?? null;\n }\n patch.openPullRequestNumber = input.mergeRequestIid;\n } else if (recorded === input.mergeRequestIid) {\n patch.openPullRequestNumber = null;\n if (input.event === 'mergeRequestMerged' && typeof item.metadata?.reviewVerdict === 'string') {\n patch.reviewVerdict = null;\n }\n }\n try {\n await this.options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n expectedRevision: item.revision,\n patch: { metadata: patch },\n });\n return;\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n }\n }\n }\n\n async #trusted(connectionIds: string[], projectId: string, username: string): Promise<boolean> {\n for (const connectionId of connectionIds) {\n try {\n const level = await this.options.gitlab.getProjectMemberAccessLevel(connectionId, projectId, username);\n if (level !== undefined) return level >= GITLAB_TRUSTED_ACCESS_LEVEL;\n } catch {\n // Try another active credential covering the same canonical project; otherwise fail closed.\n }\n }\n return false;\n }\n\n async #evaluate(\n input: {\n parsed: ParsedGitLabWebhook;\n event: FactoryGitLabEventName;\n projectId: number;\n projectPath: string;\n host: string;\n actor: FactoryRuleActor;\n orgId: string;\n factoryProjectId: string;\n factoryProject: { createdAt: Date };\n board: { board: string; initialPhase: string };\n issue: Record<string, unknown> | undefined;\n issueAuthor: string | undefined;\n issueAuthorTrusted: boolean;\n issueIid: number | undefined;\n issueSourceKey: string | undefined;\n mergeRequest: Record<string, unknown> | undefined;\n mergeRequestAuthor: string | undefined;\n mergeRequestAuthorTrusted: boolean;\n mergeRequestIid: number | undefined;\n mergeRequestKey: string | undefined;\n factoryAuthored: boolean;\n item: WorkItemRow | undefined;\n ingressIdentity: string;\n mergeRequestOpenedScope?: 'intake' | 'board';\n },\n attempt = 1,\n ): Promise<{ status: IngressStatus }> {\n const note = object(input.parsed.payload.object_attributes);\n const issueAuthor = input.issueAuthor;\n const mergeRequestAuthor = input.mergeRequestAuthor;\n const mergeRequestState = string(input.mergeRequest?.state);\n const context: FactoryGitLabRuleContext = {\n tenant: { orgId: input.orgId, projectId: input.factoryProjectId },\n actor: input.actor,\n ingress: { type: 'gitlab', id: input.ingressIdentity },\n cause: `gitlab.${input.event}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n ...(input.item\n ? {\n item: {\n id: input.item.id,\n source:\n input.item.externalSource?.type === 'pull-request'\n ? ('gitlab-pr' as const)\n : input.item.externalSource?.integrationId === 'gitlab'\n ? ('gitlab-issue' as const)\n : ('manual' as const),\n sourceKey: input.item.externalSource?.externalId ?? null,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n url: input.item.externalSource?.url ?? null,\n stages: input.item.stages,\n acceptedAt: input.item.acceptedAt,\n metadata: input.item.metadata,\n },\n board: boardForWorkItem(input.item),\n itemRevision: input.item.revision,\n ...withBoardClosure(this.options.boards, input.item),\n ...rulePhaseField(this.options.boards, input.item),\n }\n : {}),\n intake: input.board,\n event: input.event,\n deliveryId: input.parsed.deliveryId,\n factory: { createdAt: input.factoryProject.createdAt.toISOString() },\n repository: { id: input.projectId, fullName: input.projectPath, host: input.host },\n ...(input.issueIid && input.issueSourceKey && string(input.issue?.title)\n ? {\n issue: {\n number: input.issueIid,\n sourceKey: input.issueSourceKey,\n title: string(input.issue?.title)!,\n url:\n string(input.issue?.url) ??\n string(input.issue?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/issues/${input.issueIid}`,\n ...(string(input.issue?.created_at) ? { createdAt: string(input.issue?.created_at) } : {}),\n ...(string(input.issue?.updated_at) ? { updatedAt: string(input.issue?.updated_at) } : {}),\n assignees: usernames(input.parsed.payload.assignees ?? input.issue?.assignees),\n labels: labelNames(input.issue?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.issue?.labels ?? input.parsed.payload.labels),\n state: string(input.issue?.state) === 'closed' ? ('closed' as const) : ('open' as const),\n ...(issueAuthor ? { author: issueAuthor } : {}),\n authorTrusted: input.issueAuthorTrusted,\n },\n }\n : {}),\n ...(input.parsed.event === 'Note Hook' && number(note?.id)\n ? {\n note: {\n id: number(note?.id)!,\n ...(string(note?.note) ? { body: string(note?.note) } : {}),\n ...(string(note?.url) ? { url: string(note?.url) } : {}),\n ...(string(object(input.parsed.payload.user)?.username)\n ? { author: string(object(input.parsed.payload.user)?.username) }\n : {}),\n ...(string(note?.created_at) ? { createdAt: string(note?.created_at) } : {}),\n ...(string(note?.updated_at) ? { updatedAt: string(note?.updated_at) } : {}),\n },\n }\n : {}),\n ...(input.mergeRequestIid && input.mergeRequestKey && string(input.mergeRequest?.title)\n ? {\n mergeRequest: {\n number: input.mergeRequestIid,\n sourceKey: input.mergeRequestKey,\n title: string(input.mergeRequest?.title)!,\n url:\n string(input.mergeRequest?.url) ??\n string(input.mergeRequest?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/merge_requests/${input.mergeRequestIid}`,\n ...(string(input.mergeRequest?.created_at) ? { createdAt: string(input.mergeRequest?.created_at) } : {}),\n state:\n mergeRequestState === 'closed' || mergeRequestState === 'merged'\n ? ('closed' as const)\n : ('open' as const),\n draft: boolean(input.mergeRequest?.draft) ?? boolean(input.mergeRequest?.work_in_progress) ?? false,\n merged: input.event === 'mergeRequestMerged' || mergeRequestState === 'merged',\n assignees: usernames(input.parsed.payload.assignees ?? input.mergeRequest?.assignees),\n reviewers: usernames(input.parsed.payload.reviewers ?? input.mergeRequest?.reviewers),\n labels: labelNames(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n ...(mergeRequestAuthor ? { author: mergeRequestAuthor } : {}),\n authorTrusted: input.mergeRequestAuthorTrusted,\n factoryAuthored: input.factoryAuthored,\n headBranch: string(input.mergeRequest?.source_branch) ?? '',\n baseBranch: string(input.mergeRequest?.target_branch) ?? '',\n },\n }\n : {}),\n };\n const boardRules = boardEventHandlers(\n this.options.boards,\n input.mergeRequestOpenedScope === 'intake' ? undefined : eventBoardId(context),\n 'gitlab',\n );\n const rule =\n input.mergeRequestOpenedScope === 'board'\n ? hasExplicitRuleOverride(this.options.gitlab.rules, input.event)\n ? undefined\n : boardRules?.mergeRequestOpened\n : selectEventRule(this.options.gitlab.rules, boardRules, input.event);\n // Without a board handler the authoring card has nothing to answer; skip\n // recording an empty evaluation for it.\n if (input.mergeRequestOpenedScope === 'board' && !rule) return { status: 'ignored' };\n let decision: FactoryRuleDecision | void;\n let decisions: Record<string, unknown>[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : undefined;\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n decisions = validateFactoryRuleDecisions([decision]).map(entry => {\n assertFactoryDecisionTarget(\n entry,\n this.options.boards,\n input.item ? boardForWorkItem(input.item) : undefined,\n );\n return { ...entry };\n });\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 GitLab rule failed.',\n };\n }\n\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: input.orgId,\n factoryProjectId: input.factoryProjectId,\n workItemId: input.item?.id ?? null,\n ingress: { identity: input.ingressIdentity, triggerType: `gitlab.${input.event}` },\n configVersion: this.options.configVersion,\n expectedRevision: input.item?.revision ?? null,\n actor: { ...input.actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n if (committed.status === 'stale') {\n // Nothing was persisted: re-read the item and evaluate again at its fresh revision.\n if (attempt >= MAX_STALE_RULE_ATTEMPTS) {\n throw new Error(`Factory GitLab rule evaluation for ${input.ingressIdentity} kept losing revision races.`);\n }\n const fresh = input.item ? await this.options.storage.get({ orgId: input.orgId, id: input.item.id }) : null;\n if (input.item && !fresh) return { status: 'missing' };\n return this.#evaluate({ ...input, item: fresh ?? undefined }, attempt + 1);\n }\n return { status: committed.status };\n }\n}\n\nexport function attachGitLabRules(\n gitlab: GitLabRulesIntegration,\n context: IntegrationContext,\n): ((event: ParsedGitLabWebhook) => Promise<unknown>) | undefined {\n if (!context.runtime) return undefined;\n const rules = new GitLabRules({\n gitlab,\n sourceControl: context.storage.sourceControl,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n intake: context.storage.intake,\n configVersion: context.runtime.configVersion,\n boards: context.runtime.boards,\n });\n return event => rules.ingest(event);\n}\n"],"mappings":"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"}
@@ -31,10 +31,10 @@ declare function incidentioFollowUpObserved(context: FactoryIncidentioRuleContex
31
31
  declare function incidentioFollowUpClosed(context: FactoryIncidentioRuleContext): {
32
32
  readonly type: "transition";
33
33
  readonly idempotencyKey: `${string}:follow-up-closed`;
34
- readonly board: "work";
35
- readonly stage: "done" | "canceled";
34
+ readonly board: import("../../rules/types.js").FactoryRuleBoard;
35
+ readonly stage: string;
36
36
  readonly message: {
37
- readonly text: `incident.io follow-up ${string} was canceled; this Work card was moved to Done.` | `incident.io follow-up ${string} was canceled; this Work card was moved to Canceled.` | `incident.io follow-up ${string} was completed; this Work card was moved to Done.` | `incident.io follow-up ${string} was completed; this Work card was moved to Canceled.`;
37
+ readonly text: `incident.io follow-up ${string} was canceled; this ${string} card was moved to ${string}.` | `incident.io follow-up ${string} was completed; this ${string} card was moved to ${string}.`;
38
38
  };
39
39
  } | undefined;
40
40
  export declare const defaultIncidentioRules: Readonly<{
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/default-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC5B,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAC3C,MAAM,CAAC,0BAA0B,EAAE,kBAAkB,CAAC,4BAA4B,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CACxG,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CACzC,MAAM,CAAC,0BAA0B,EAAE,kBAAkB,CAAC,4BAA4B,CAAC,GAAG,IAAI,CAAC,CAC5F,CAAC;AAEF,iBAAS,0BAA0B,CAAC,OAAO,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;4BAuBI,MAAM,EAAE;;;yBAG1C,MAAM,EAAE;;;;cAKlD;AAED,iBAAS,wBAAwB,CAAC,OAAO,EAAE,4BAA4B;;;;;;;;cAkBtE;AAED,eAAO,MAAM,sBAAsB;;;EAGF,CAAC;AAElC,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,uBAAuB,GAAG,oBAAoB,CAwBhG"}
1
+ {"version":3,"file":"default-rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/default-rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC5B,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAC3C,MAAM,CAAC,0BAA0B,EAAE,kBAAkB,CAAC,4BAA4B,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CACxG,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CACzC,MAAM,CAAC,0BAA0B,EAAE,kBAAkB,CAAC,4BAA4B,CAAC,GAAG,IAAI,CAAC,CAC5F,CAAC;AAEF,iBAAS,0BAA0B,CAAC,OAAO,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;4BAuBI,MAAM,EAAE;;;yBAG1C,MAAM,EAAE;;;;cAKlD;AAED,iBAAS,wBAAwB,CAAC,OAAO,EAAE,4BAA4B;;;;;;;;cAoBtE;AAED,eAAO,MAAM,sBAAsB;;;EAGF,CAAC;AAElC,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,uBAAuB,GAAG,oBAAoB,CAwBhG"}
@@ -1,3 +1,4 @@
1
+ import { recordExplicitRuleOverrides } from "../../rules/event-rules.js";
1
2
  import { incidentioClaimKey } from "./claim.js";
2
3
  //#region src/integrations/incidentio/default-rules.ts
3
4
  function incidentioFollowUpObserved(context) {
@@ -32,17 +33,18 @@ function incidentioFollowUpObserved(context) {
32
33
  }
33
34
  function incidentioFollowUpClosed(context) {
34
35
  if (!context.item || context.item.source !== "incidentio-follow-up") return;
35
- if (context.board !== "work") return;
36
- if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
36
+ const closure = context.boardClosure;
37
+ if (!context.board || !closure || closure.itemTerminal) return;
37
38
  const stateType = context.issue.stateType;
38
39
  if (stateType !== "completed" && stateType !== "canceled") return;
39
40
  const canceled = stateType === "canceled";
41
+ const target = canceled ? closure.canceled : closure.completed;
40
42
  return {
41
43
  type: "transition",
42
44
  idempotencyKey: `${context.ingress.id}:follow-up-closed`,
43
- board: "work",
44
- stage: canceled ? "canceled" : "done",
45
- message: { text: `incident.io follow-up ${context.issue.identifier} was ${canceled ? "canceled" : "completed"}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
45
+ board: context.board,
46
+ stage: target.phase,
47
+ message: { text: `incident.io follow-up ${context.issue.identifier} was ${canceled ? "canceled" : "completed"}; this ${closure.boardTitle} card was moved to ${target.title}.` }
46
48
  };
47
49
  }
48
50
  const defaultIncidentioRules = Object.freeze({
@@ -58,7 +60,7 @@ function resolveIncidentioRules(overrides) {
58
60
  if (handler !== void 0 && handler !== null && typeof handler !== "function") throw new Error(`incident.io rule ${key} must be a function, null, or undefined.`);
59
61
  if (handler !== void 0) rules[key] = handler;
60
62
  }
61
- return Object.freeze(rules);
63
+ return recordExplicitRuleOverrides(Object.freeze(rules), overrides);
62
64
  }
63
65
  //#endregion
64
66
  export { defaultIncidentioRules, resolveIncidentioRules };
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/incidentio/default-rules.ts"],"sourcesContent":["import type {\n FactoryIncidentioEventName,\n FactoryIncidentioRuleContext,\n FactoryRuleHandler,\n} from '../../rules/types.js';\nimport { incidentioClaimKey } from './claim.js';\n\nexport type IncidentioRuleOverrides = Partial<\n Record<FactoryIncidentioEventName, FactoryRuleHandler<FactoryIncidentioRuleContext> | null | undefined>\n>;\nexport type IncidentioEventRules = Readonly<\n Record<FactoryIncidentioEventName, FactoryRuleHandler<FactoryIncidentioRuleContext> | null>\n>;\n\nfunction incidentioFollowUpObserved(context: FactoryIncidentioRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:follow-up-triage`,\n // A source bound to a custom board lands on that board's initial phase;\n // otherwise the follow-up waits in Work Intake.\n board: context.intake?.board ?? 'work',\n source: 'incidentio-follow-up',\n sourceKey: context.issue.id,\n claimKey: incidentioClaimKey(context.issue.id),\n title: context.issue.title,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'intake',\n metadata: {\n identifier: context.issue.identifier,\n issueRef: context.issue.id,\n sourceCreatedAt: context.issue.createdAt,\n state: context.issue.state,\n stateType: context.issue.stateType,\n priority: context.issue.priorityLabel,\n incident: context.issue.incident,\n assignee: context.issue.assignee,\n assignees: (context.issue.assignee ? [context.issue.assignee] : []) as string[],\n creator: context.issue.author,\n author: context.issue.author,\n labels: [...context.issue.labels] as string[],\n createdAt: context.issue.createdAt,\n updatedAt: context.issue.updatedAt,\n },\n } as const;\n}\n\nfunction incidentioFollowUpClosed(context: FactoryIncidentioRuleContext) {\n if (!context.item || context.item.source !== 'incidentio-follow-up') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:follow-up-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `incident.io follow-up ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nexport const defaultIncidentioRules = Object.freeze({\n followUpObserved: incidentioFollowUpObserved,\n followUpClosed: incidentioFollowUpClosed,\n} satisfies IncidentioEventRules);\n\nexport function resolveIncidentioRules(overrides?: IncidentioRuleOverrides): IncidentioEventRules {\n if (\n overrides !== undefined &&\n (overrides === null ||\n typeof overrides !== 'object' ||\n Array.isArray(overrides) ||\n ![Object.prototype, null].includes(Object.getPrototypeOf(overrides)))\n ) {\n throw new Error('incident.io rules must be a plain object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryIncidentioRuleContext> | null> = {\n ...defaultIncidentioRules,\n };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultIncidentioRules, key)) {\n throw new Error(`Unknown incident.io rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryIncidentioEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`incident.io rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return Object.freeze(rules) as IncidentioEventRules;\n}\n"],"mappings":";;AAcA,SAAS,2BAA2B,SAAuC;CACzE,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EAGtC,OAAO,QAAQ,QAAQ,SAAS;EAChC,QAAQ;EACR,WAAW,QAAQ,MAAM;EACzB,UAAU,mBAAmB,QAAQ,MAAM,EAAE;EAC7C,OAAO,QAAQ,MAAM;EACrB,KAAK,QAAQ,MAAM;EACnB,OAAO,QAAQ,QAAQ,gBAAgB;EACvC,UAAU;GACR,YAAY,QAAQ,MAAM;GAC1B,UAAU,QAAQ,MAAM;GACxB,iBAAiB,QAAQ,MAAM;GAC/B,OAAO,QAAQ,MAAM;GACrB,WAAW,QAAQ,MAAM;GACzB,UAAU,QAAQ,MAAM;GACxB,UAAU,QAAQ,MAAM;GACxB,UAAU,QAAQ,MAAM;GACxB,WAAY,QAAQ,MAAM,WAAW,CAAC,QAAQ,MAAM,QAAQ,IAAI,CAAC;GACjE,SAAS,QAAQ,MAAM;GACvB,QAAQ,QAAQ,MAAM;GACtB,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,WAAW,QAAQ,MAAM;GACzB,WAAW,QAAQ,MAAM;EAC3B;CACF;AACF;AAEA,SAAS,yBAAyB,SAAuC;CACvE,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,wBAAwB;CACrE,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAEjF,MAAM,YAAY,QAAQ,MAAM;CAChC,IAAI,cAAc,eAAe,cAAc,YAAY;CAC3D,MAAM,WAAW,cAAc;CAC/B,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MAAM,yBAAyB,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,gCAAgC,WAAW,aAAa,OAAO,GACpK;CACF;AACF;AAEA,MAAa,yBAAyB,OAAO,OAAO;CAClD,kBAAkB;CAClB,gBAAgB;AAClB,CAAgC;AAEhC,SAAgB,uBAAuB,WAA2D;CAChG,IACE,cAAc,KAAA,MACb,cAAc,QACb,OAAO,cAAc,YACrB,MAAM,QAAQ,SAAS,KACvB,CAAC,CAAC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS,OAAO,eAAe,SAAS,CAAC,IAErE,MAAM,IAAI,MAAM,2CAA2C;CAE7D,MAAM,QAAiF,EACrF,GAAG,uBACL;CACA,KAAK,MAAM,OAAO,QAAQ,QAAQ,aAAa,CAAC,CAAC,GAAG;EAClD,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,OAAO,wBAAwB,GAAG,GACvE,MAAM,IAAI,MAAM,mCAAmC,OAAO,GAAG,EAAE,EAAE;EAEnE,MAAM,UAAU,YAAY;EAC5B,IAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,OAAO,YAAY,YAClE,MAAM,IAAI,MAAM,oBAAoB,IAAI,yCAAyC;EAEnF,IAAI,YAAY,KAAA,GAAW,MAAM,OAAO;CAC1C;CACA,OAAO,OAAO,OAAO,KAAK;AAC5B"}
1
+ {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/incidentio/default-rules.ts"],"sourcesContent":["import { recordExplicitRuleOverrides } from '../../rules/event-rules.js';\nimport type {\n FactoryIncidentioEventName,\n FactoryIncidentioRuleContext,\n FactoryRuleHandler,\n} from '../../rules/types.js';\nimport { incidentioClaimKey } from './claim.js';\n\nexport type IncidentioRuleOverrides = Partial<\n Record<FactoryIncidentioEventName, FactoryRuleHandler<FactoryIncidentioRuleContext> | null | undefined>\n>;\nexport type IncidentioEventRules = Readonly<\n Record<FactoryIncidentioEventName, FactoryRuleHandler<FactoryIncidentioRuleContext> | null>\n>;\n\nfunction incidentioFollowUpObserved(context: FactoryIncidentioRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:follow-up-triage`,\n // A source bound to a custom board lands on that board's initial phase;\n // otherwise the follow-up waits in Work Intake.\n board: context.intake?.board ?? 'work',\n source: 'incidentio-follow-up',\n sourceKey: context.issue.id,\n claimKey: incidentioClaimKey(context.issue.id),\n title: context.issue.title,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'intake',\n metadata: {\n identifier: context.issue.identifier,\n issueRef: context.issue.id,\n sourceCreatedAt: context.issue.createdAt,\n state: context.issue.state,\n stateType: context.issue.stateType,\n priority: context.issue.priorityLabel,\n incident: context.issue.incident,\n assignee: context.issue.assignee,\n assignees: (context.issue.assignee ? [context.issue.assignee] : []) as string[],\n creator: context.issue.author,\n author: context.issue.author,\n labels: [...context.issue.labels] as string[],\n createdAt: context.issue.createdAt,\n updatedAt: context.issue.updatedAt,\n },\n } as const;\n}\n\nfunction incidentioFollowUpClosed(context: FactoryIncidentioRuleContext) {\n if (!context.item || context.item.source !== 'incidentio-follow-up') return;\n // The card's board decides where a closed source sends it; boards that declare no\n // `sourceClosed` mapping keep the card where it is, as do cards already finished.\n const closure = context.boardClosure;\n if (!context.board || !closure || closure.itemTerminal) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n const target = canceled ? closure.canceled : closure.completed;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:follow-up-closed`,\n board: context.board,\n stage: target.phase,\n message: {\n text: `incident.io follow-up ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this ${closure.boardTitle} card was moved to ${target.title}.`,\n },\n } as const;\n}\n\nexport const defaultIncidentioRules = Object.freeze({\n followUpObserved: incidentioFollowUpObserved,\n followUpClosed: incidentioFollowUpClosed,\n} satisfies IncidentioEventRules);\n\nexport function resolveIncidentioRules(overrides?: IncidentioRuleOverrides): IncidentioEventRules {\n if (\n overrides !== undefined &&\n (overrides === null ||\n typeof overrides !== 'object' ||\n Array.isArray(overrides) ||\n ![Object.prototype, null].includes(Object.getPrototypeOf(overrides)))\n ) {\n throw new Error('incident.io rules must be a plain object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryIncidentioRuleContext> | null> = {\n ...defaultIncidentioRules,\n };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultIncidentioRules, key)) {\n throw new Error(`Unknown incident.io rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryIncidentioEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`incident.io rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return recordExplicitRuleOverrides(Object.freeze(rules) as IncidentioEventRules, overrides);\n}\n"],"mappings":";;;AAeA,SAAS,2BAA2B,SAAuC;CACzE,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EAGtC,OAAO,QAAQ,QAAQ,SAAS;EAChC,QAAQ;EACR,WAAW,QAAQ,MAAM;EACzB,UAAU,mBAAmB,QAAQ,MAAM,EAAE;EAC7C,OAAO,QAAQ,MAAM;EACrB,KAAK,QAAQ,MAAM;EACnB,OAAO,QAAQ,QAAQ,gBAAgB;EACvC,UAAU;GACR,YAAY,QAAQ,MAAM;GAC1B,UAAU,QAAQ,MAAM;GACxB,iBAAiB,QAAQ,MAAM;GAC/B,OAAO,QAAQ,MAAM;GACrB,WAAW,QAAQ,MAAM;GACzB,UAAU,QAAQ,MAAM;GACxB,UAAU,QAAQ,MAAM;GACxB,UAAU,QAAQ,MAAM;GACxB,WAAY,QAAQ,MAAM,WAAW,CAAC,QAAQ,MAAM,QAAQ,IAAI,CAAC;GACjE,SAAS,QAAQ,MAAM;GACvB,QAAQ,QAAQ,MAAM;GACtB,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,WAAW,QAAQ,MAAM;GACzB,WAAW,QAAQ,MAAM;EAC3B;CACF;AACF;AAEA,SAAS,yBAAyB,SAAuC;CACvE,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,wBAAwB;CAGrE,MAAM,UAAU,QAAQ;CACxB,IAAI,CAAC,QAAQ,SAAS,CAAC,WAAW,QAAQ,cAAc;CAExD,MAAM,YAAY,QAAQ,MAAM;CAChC,IAAI,cAAc,eAAe,cAAc,YAAY;CAC3D,MAAM,WAAW,cAAc;CAC/B,MAAM,SAAS,WAAW,QAAQ,WAAW,QAAQ;CACrD,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO,QAAQ;EACf,OAAO,OAAO;EACd,SAAS,EACP,MAAM,yBAAyB,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,SAAS,QAAQ,WAAW,qBAAqB,OAAO,MAAM,GACnK;CACF;AACF;AAEA,MAAa,yBAAyB,OAAO,OAAO;CAClD,kBAAkB;CAClB,gBAAgB;AAClB,CAAgC;AAEhC,SAAgB,uBAAuB,WAA2D;CAChG,IACE,cAAc,KAAA,MACb,cAAc,QACb,OAAO,cAAc,YACrB,MAAM,QAAQ,SAAS,KACvB,CAAC,CAAC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS,OAAO,eAAe,SAAS,CAAC,IAErE,MAAM,IAAI,MAAM,2CAA2C;CAE7D,MAAM,QAAiF,EACrF,GAAG,uBACL;CACA,KAAK,MAAM,OAAO,QAAQ,QAAQ,aAAa,CAAC,CAAC,GAAG;EAClD,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,OAAO,wBAAwB,GAAG,GACvE,MAAM,IAAI,MAAM,mCAAmC,OAAO,GAAG,EAAE,EAAE;EAEnE,MAAM,UAAU,YAAY;EAC5B,IAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,OAAO,YAAY,YAClE,MAAM,IAAI,MAAM,oBAAoB,IAAI,yCAAyC;EAEnF,IAAI,YAAY,KAAA,GAAW,MAAM,OAAO;CAC1C;CACA,OAAO,4BAA4B,OAAO,OAAO,KAAK,GAA2B,SAAS;AAC5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAoB/D,MAAM,WAAW,sBAAsB;IACrC,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,EAAE,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,oFAAoF;IACpF,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,KAAK,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAE1D,qBAAa,eAAe;;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,sBAAsB;IAEtD,MAAM,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CA+IlG;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,EACpD,OAAO,EAAE,kBAAkB,GAC1B,CAAC,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAUnE"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/rules.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAoB/D,MAAM,WAAW,sBAAsB;IACrC,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,EAAE,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,oFAAoF;IACpF,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,KAAK,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAE1D,qBAAa,eAAe;;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,sBAAsB;IAEtD,MAAM,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAqJlG;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,EACpD,OAAO,EAAE,kBAAkB,GAC1B,CAAC,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAUnE"}
@@ -1,6 +1,7 @@
1
1
  import { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from "../../rules/validation.js";
2
- import { boardForWorkItem, isTerminalWorkItem } from "../../boards/semantics.js";
2
+ import { boardEventHandlers, boardForWorkItem, eventBoardId, isTerminalWorkItem, rulePhaseField, withBoardClosure } from "../../boards/semantics.js";
3
3
  import "../../boards/index.js";
4
+ import { selectEventRule } from "../../rules/event-rules.js";
4
5
  import { incidentioClaimKey } from "./claim.js";
5
6
  //#region src/integrations/incidentio/rules.ts
6
7
  const RULE_TIMEOUT_MS = 5e3;
@@ -109,7 +110,9 @@ var IncidentioRules = class {
109
110
  metadata: relatedItem.metadata
110
111
  },
111
112
  board: boardForWorkItem(relatedItem),
112
- itemRevision: relatedItem.revision
113
+ itemRevision: relatedItem.revision,
114
+ ...withBoardClosure(this.options.boards, relatedItem),
115
+ ...rulePhaseField(this.options.boards, relatedItem)
113
116
  } : {},
114
117
  ...boundBoard ? { intake: {
115
118
  board: boundBoard.id,
@@ -118,7 +121,7 @@ var IncidentioRules = class {
118
121
  event,
119
122
  issue
120
123
  };
121
- const rule = this.options.incidentioRules[context.event];
124
+ const rule = selectEventRule(this.options.incidentioRules, boardEventHandlers(this.options.boards, eventBoardId(context), "incidentio"), context.event);
122
125
  let decision;
123
126
  let decisions = [];
124
127
  let outcome = { status: "accepted" };