@mastra/factory 0.21.1-alpha.3 → 0.22.0-alpha.4

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 (174) hide show
  1. package/dist/capabilities/version-control.d.ts +13 -8
  2. package/dist/capabilities/version-control.d.ts.map +1 -1
  3. package/dist/environment/build-runner.d.ts +72 -0
  4. package/dist/environment/build-runner.d.ts.map +1 -0
  5. package/dist/environment/build-runner.js +131 -0
  6. package/dist/environment/build-runner.js.map +1 -0
  7. package/dist/environment/build-schedule.d.ts +40 -0
  8. package/dist/environment/build-schedule.d.ts.map +1 -0
  9. package/dist/environment/build-schedule.js +94 -0
  10. package/dist/environment/build-schedule.js.map +1 -0
  11. package/dist/environment/build-workflow.d.ts +62 -0
  12. package/dist/environment/build-workflow.d.ts.map +1 -0
  13. package/dist/environment/build-workflow.js +149 -0
  14. package/dist/environment/build-workflow.js.map +1 -0
  15. package/dist/environment/build.d.ts +111 -0
  16. package/dist/environment/build.d.ts.map +1 -0
  17. package/dist/environment/build.js +344 -0
  18. package/dist/environment/build.js.map +1 -0
  19. package/dist/environment/heads.d.ts +36 -0
  20. package/dist/environment/heads.d.ts.map +1 -0
  21. package/dist/environment/heads.js +60 -0
  22. package/dist/environment/heads.js.map +1 -0
  23. package/dist/factory.d.ts +18 -4
  24. package/dist/factory.d.ts.map +1 -1
  25. package/dist/factory.js +89 -15
  26. package/dist/factory.js.map +1 -1
  27. package/dist/index.d.ts +2 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -2
  30. package/dist/integrations/base.d.ts +17 -2
  31. package/dist/integrations/base.d.ts.map +1 -1
  32. package/dist/integrations/github/commits.d.ts +11 -0
  33. package/dist/integrations/github/commits.d.ts.map +1 -1
  34. package/dist/integrations/github/commits.js +20 -1
  35. package/dist/integrations/github/commits.js.map +1 -1
  36. package/dist/integrations/github/config.d.ts +3 -3
  37. package/dist/integrations/github/config.d.ts.map +1 -1
  38. package/dist/integrations/github/config.js +1 -1
  39. package/dist/integrations/github/config.js.map +1 -1
  40. package/dist/integrations/github/routes.d.ts +2 -2
  41. package/dist/integrations/github/routes.d.ts.map +1 -1
  42. package/dist/integrations/github/routes.js +6 -5
  43. package/dist/integrations/github/routes.js.map +1 -1
  44. package/dist/integrations/github/rules.d.ts +3 -1
  45. package/dist/integrations/github/rules.d.ts.map +1 -1
  46. package/dist/integrations/github/rules.js +31 -1
  47. package/dist/integrations/github/rules.js.map +1 -1
  48. package/dist/integrations/github/sandbox.d.ts +35 -0
  49. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  50. package/dist/integrations/github/sandbox.js +203 -2
  51. package/dist/integrations/github/sandbox.js.map +1 -1
  52. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  53. package/dist/integrations/github/session-subscriptions.js +117 -62
  54. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  55. package/dist/integrations/github/token-refresh.d.ts +7 -0
  56. package/dist/integrations/github/token-refresh.d.ts.map +1 -1
  57. package/dist/integrations/github/token-refresh.js.map +1 -1
  58. package/dist/integrations/github/webhook.js.map +1 -1
  59. package/dist/integrations/gitlab/agent-tools.js +1 -1
  60. package/dist/integrations/gitlab/routes.d.ts +2 -2
  61. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  62. package/dist/integrations/gitlab/routes.js.map +1 -1
  63. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  64. package/dist/integrations/gitlab/session-subscriptions.js +8 -7
  65. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  66. package/dist/integrations/incidentio/agent-tools.js +1 -1
  67. package/dist/integrations/jira/agent-tools.js +1 -1
  68. package/dist/integrations/linear/agent-tools.js +1 -1
  69. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  70. package/dist/integrations/platform/github/integration.js +46 -2
  71. package/dist/integrations/platform/github/integration.js.map +1 -1
  72. package/dist/integrations/platform/jira/agent-tools.js +1 -1
  73. package/dist/integrations/slack/slack.d.ts.map +1 -1
  74. package/dist/integrations/slack/slack.js +15 -16
  75. package/dist/integrations/slack/slack.js.map +1 -1
  76. package/dist/integrations/subscription-session.js +1 -1
  77. package/dist/packages/_internals/workspace/dist/index.js +21 -1
  78. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  79. package/dist/routes/contracts.d.ts +386 -2
  80. package/dist/routes/contracts.d.ts.map +1 -1
  81. package/dist/routes/contracts.js +166 -1
  82. package/dist/routes/contracts.js.map +1 -1
  83. package/dist/routes/fs.d.ts +25 -0
  84. package/dist/routes/fs.d.ts.map +1 -1
  85. package/dist/routes/fs.js +142 -46
  86. package/dist/routes/fs.js.map +1 -1
  87. package/dist/routes/projects.d.ts +6 -0
  88. package/dist/routes/projects.d.ts.map +1 -1
  89. package/dist/routes/projects.js +344 -1
  90. package/dist/routes/projects.js.map +1 -1
  91. package/dist/routes/source-control-sessions.d.ts +2 -0
  92. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  93. package/dist/routes/source-control-sessions.js +36 -1
  94. package/dist/routes/source-control-sessions.js.map +1 -1
  95. package/dist/routes/supervisor.js +1 -1
  96. package/dist/routes/surface.d.ts +6 -3
  97. package/dist/routes/surface.d.ts.map +1 -1
  98. package/dist/routes/surface.js +21 -34
  99. package/dist/routes/surface.js.map +1 -1
  100. package/dist/rules/binding-context.d.ts +1 -1
  101. package/dist/rules/binding-context.d.ts.map +1 -1
  102. package/dist/rules/binding-context.js +1 -1
  103. package/dist/rules/binding-context.js.map +1 -1
  104. package/dist/rules/review-source-tool.js +1 -1
  105. package/dist/rules/start-coordinator.d.ts.map +1 -1
  106. package/dist/rules/start-coordinator.js +7 -2
  107. package/dist/rules/start-coordinator.js.map +1 -1
  108. package/dist/rules/tools.js +1 -1
  109. package/dist/sandbox/callback-factory-sandbox.d.ts +15 -0
  110. package/dist/sandbox/callback-factory-sandbox.d.ts.map +1 -0
  111. package/dist/sandbox/callback-factory-sandbox.js +22 -0
  112. package/dist/sandbox/callback-factory-sandbox.js.map +1 -0
  113. package/dist/sandbox/local-factory-sandbox.d.ts +20 -0
  114. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -0
  115. package/dist/sandbox/local-factory-sandbox.js +27 -0
  116. package/dist/sandbox/local-factory-sandbox.js.map +1 -0
  117. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  118. package/dist/sandbox/session-retirement.js +23 -3
  119. package/dist/sandbox/session-retirement.js.map +1 -1
  120. package/dist/sandbox/session-sandbox.d.ts +82 -40
  121. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  122. package/dist/sandbox/session-sandbox.js +130 -13
  123. package/dist/sandbox/session-sandbox.js.map +1 -1
  124. package/dist/sandbox/workdir.d.ts +10 -0
  125. package/dist/sandbox/workdir.d.ts.map +1 -1
  126. package/dist/sandbox/workdir.js +10 -1
  127. package/dist/sandbox/workdir.js.map +1 -1
  128. package/dist/session/environment-repositories.d.ts +31 -0
  129. package/dist/session/environment-repositories.d.ts.map +1 -0
  130. package/dist/session/environment-repositories.js +89 -0
  131. package/dist/session/environment-repositories.js.map +1 -0
  132. package/dist/session/environment-state-processor.d.ts +59 -0
  133. package/dist/session/environment-state-processor.d.ts.map +1 -0
  134. package/dist/session/environment-state-processor.js +105 -0
  135. package/dist/session/environment-state-processor.js.map +1 -0
  136. package/dist/session/factory-session.d.ts +26 -35
  137. package/dist/session/factory-session.d.ts.map +1 -1
  138. package/dist/session/factory-session.js +34 -71
  139. package/dist/session/factory-session.js.map +1 -1
  140. package/dist/session/filesystem-capture.d.ts +13 -0
  141. package/dist/session/filesystem-capture.d.ts.map +1 -1
  142. package/dist/session/filesystem-capture.js +73 -14
  143. package/dist/session/filesystem-capture.js.map +1 -1
  144. package/dist/session/source-control-tools.d.ts +1 -1
  145. package/dist/session/source-control-tools.d.ts.map +1 -1
  146. package/dist/session/source-control-tools.js +144 -71
  147. package/dist/session/source-control-tools.js.map +1 -1
  148. package/dist/spa-static.js +2 -2
  149. package/dist/storage/domains/audit/agent-audit.d.ts +6 -1
  150. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  151. package/dist/storage/domains/audit/agent-audit.js +31 -13
  152. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  153. package/dist/storage/domains/projects/base.d.ts +115 -2
  154. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  155. package/dist/storage/domains/projects/base.js +219 -12
  156. package/dist/storage/domains/projects/base.js.map +1 -1
  157. package/dist/storage/domains/source-control/base.d.ts +123 -7
  158. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  159. package/dist/storage/domains/source-control/base.js +406 -33
  160. package/dist/storage/domains/source-control/base.js.map +1 -1
  161. package/dist/storage/domains/source-control/inmemory.d.ts +19 -6
  162. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  163. package/dist/storage/domains/source-control/inmemory.js +90 -2
  164. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  165. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  166. package/dist/storage/domains/work-items/base.js +12 -5
  167. package/dist/storage/domains/work-items/base.js.map +1 -1
  168. package/dist/supervisor/read-tools.js +1 -1
  169. package/dist/supervisor/write-tools.js +1 -1
  170. package/dist/workspace.d.ts +50 -7
  171. package/dist/workspace.d.ts.map +1 -1
  172. package/dist/workspace.js +344 -24
  173. package/dist/workspace.js.map +1 -1
  174. package/package.json +7 -7
@@ -285,6 +285,10 @@ var GithubRules = class {
285
285
  const repositoryId = number(repository?.id);
286
286
  const repositoryName = string(repository?.full_name);
287
287
  const login = string(object(parsed.payload.sender)?.login);
288
+ if (parsed.event === "push" && installationId && repositoryId) {
289
+ await this.#handOffPush(parsed, installationId, repositoryId);
290
+ return { status: "ignored" };
291
+ }
288
292
  if (!event && !labelChange || !installationId || !repositoryId || !repositoryName || !login) return { status: "ignored" };
289
293
  const projects = await this.options.sourceControl.projectRepositories.listByExternalRepository({
290
294
  installationExternalId: String(installationId),
@@ -297,6 +301,31 @@ var GithubRules = class {
297
301
  if (results.some((result) => result.status === "replayed")) return { status: "replayed" };
298
302
  return results[0] ?? { status: "ignored" };
299
303
  }
304
+ /**
305
+ * A push is not a work-item event; it only reaches the environment build
306
+ * hand-off, once per project the repository is linked to. The hook decides
307
+ * whether the project builds on push.
308
+ */
309
+ async #handOffPush(parsed, installationId, repositoryId) {
310
+ const onRepositoryPush = this.options.onRepositoryPush;
311
+ const ref = string(parsed.payload.ref);
312
+ const defaultBranch = string(object(parsed.payload.repository)?.default_branch);
313
+ if (!onRepositoryPush || !ref || !defaultBranch) return;
314
+ const projects = await this.options.sourceControl.projectRepositories.listByExternalRepository({
315
+ installationExternalId: String(installationId),
316
+ repositoryExternalId: String(repositoryId)
317
+ });
318
+ for (const project of projects) onRepositoryPush({
319
+ orgId: project.orgId,
320
+ factoryProjectId: project.factoryProjectId,
321
+ projectRepository: {
322
+ id: project.projectRepository.id,
323
+ inEnvironment: project.projectRepository.inEnvironment
324
+ },
325
+ ref,
326
+ defaultBranch
327
+ });
328
+ }
300
329
  async #ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, project) {
301
330
  const factoryProject = await this.options.projects.get({
302
331
  orgId: project.orgId,
@@ -971,7 +1000,8 @@ function githubRulesOptions(github, context) {
971
1000
  storage: context.runtime.workItems,
972
1001
  configVersion: context.runtime.configVersion,
973
1002
  boards: context.runtime.boards,
974
- intake: context.storage.intake
1003
+ intake: context.storage.intake,
1004
+ ...context.hooks?.onRepositoryPush ? { onRepositoryPush: context.hooks.onRepositoryPush } : {}
975
1005
  };
976
1006
  }
977
1007
  function attachGithubRules(github, context) {
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","names":["#intakeTarget","#relocateLabeledIssue","#relatedItem","#ingestProject","#factoryMentionTarget","#isFactoryLogin","#linkedClosureItem","#resolveItem"],"sources":["../../../src/integrations/github/rules.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport {\n boardEventHandlers,\n boardForWorkItem,\n eventBoardId,\n rulePhaseField,\n withBoardClosure,\n workItemPhaseSemantics,\n} from '../../boards/index.js';\nimport type { BoardRegistry } from '../../boards/index.js';\nimport { cardLabels, moveCardToBoard } from '../../boards/relocate.js';\nimport { selectEventRule } from '../../rules/event-rules.js';\nimport type {\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n} from '../../rules/types.js';\nimport { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from '../../rules/validation.js';\nimport { resolveIntakeLabelRoute } from '../../storage/domains/intake/base.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/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 type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport {\n FACTORY_PULL_REQUEST_RECONCILIATION_KEY,\n WorkItemUpdateConflictError,\n} from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport { recordPullRequestHead } from '../pull-request-head.js';\nimport type { GithubAppIdentity } from './app-identity.js';\nimport type { GithubEventRules } from './default-rules.js';\nimport type { GithubRepositoryPermission } from './integration.js';\nimport { changeRequestTargetKey } from './subscriptions.js';\nimport type { ParsedGithubWebhook } from './webhook.js';\n\nconst TRUSTED_PERMISSIONS = new Set(['write', 'admin']);\nconst RULE_TIMEOUT_MS = 5_000;\nconst FACTORY_TRIAGE_COMMENT_MARKER = '<!-- mastra-factory-triage -->';\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\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 timestamp(value: unknown): number | undefined {\n const parsed = typeof value === 'string' ? Date.parse(value) : NaN;\n return Number.isNaN(parsed) ? undefined : parsed;\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\nfunction boolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction actorLogins(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(actor => {\n const login = string(object(actor)?.login);\n return login ? [login] : [];\n });\n}\n\nfunction sameLabels(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((label, index) => label === b[index]);\n}\n\nfunction labelNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(label => {\n if (typeof label === 'string') return label ? [label] : [];\n const name = string(object(label)?.name);\n return name ? [name] : [];\n });\n}\n\nfunction parseFactoryReviewCommand(\n body: string | undefined,\n target: string | undefined,\n): { command: 'review' | 're-review'; target: string } | undefined {\n if (!body || !target) return undefined;\n const firstLine = body\n .split('\\n')\n .map(line => line.trim())\n .find(line => line.length > 0)\n ?.toLowerCase();\n if (!firstLine) return undefined;\n const mention = `@${target.toLowerCase().replace(/\\[bot\\]$/, '')}`;\n if (firstLine === `${mention} review`) return { command: 'review', target };\n if (firstLine === `${mention} re-review`) return { command: 're-review', target };\n return undefined;\n}\n\nfunction eventName(parsed: ParsedGithubWebhook): FactoryGithubEventName | undefined {\n const action = string(parsed.payload.action);\n if (parsed.event === 'issues' && action === 'opened') return 'issueOpened';\n if (parsed.event === 'issues' && action === 'edited') {\n const changes = object(parsed.payload.changes);\n return object(changes?.title) || object(changes?.body) ? 'issueEdited' : undefined;\n }\n if (parsed.event === 'issues' && action === 'closed') return 'issueClosed';\n if (parsed.event === 'issue_comment') {\n const issue = object(parsed.payload.issue);\n // A comment on a PR arrives as `issue_comment` with `issue.pull_request` set.\n // It routes to the PR's own event so it binds to the authoring Work item via\n // provenance instead of being mistaken for a comment on an issue of the same\n // number. Only `created` matters: edits and deletions of an existing comment\n // are not new feedback to act on.\n if (object(issue?.pull_request)) {\n return action === 'created' ? 'pullRequestCommentCreated' : undefined;\n }\n if (action === 'created') return 'issueCommentCreated';\n if (action === 'edited') return 'issueCommentEdited';\n if (action === 'deleted') return 'issueCommentDeleted';\n }\n if (parsed.event === 'pull_request' && action === 'opened') return 'pullRequestOpened';\n if (parsed.event === 'pull_request' && action === 'synchronize') return 'pullRequestUpdated';\n if (parsed.event === 'pull_request' && action === 'closed') {\n return boolean(object(parsed.payload.pull_request)?.merged) ? 'pullRequestMerged' : 'pullRequestClosed';\n }\n if (parsed.event === 'pull_request' && action === 'review_requested') return 'pullRequestReviewRequested';\n if (parsed.event === 'pull_request_review' && action === 'submitted') return 'pullRequestReviewSubmitted';\n return undefined;\n}\n\n/**\n * Canonical source keys (`github-issue:N`, `github-pr:N`) do not identify a\n * repository, so a project linked to several repositories could bind repo A's\n * event to repo B's same-numbered card. The card's intake-stamped URL is\n * authoritative; the intake-stamped `githubRepositoryId` covers URL-less\n * cards. A card with neither signal cannot be attributed by number alone.\n */\nconst MAX_STALE_RULE_ATTEMPTS = 3;\n\nexport function cardBelongsToRepository(item: WorkItemRow, repositoryId: number, repositoryFullName: string): boolean {\n const url = item.externalSource?.url;\n if (url) {\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/(?:issues|pull)\\/\\d+(?:[/?#]|$)/.exec(url);\n if (match && match[1] === repositoryFullName) return true;\n }\n // A renamed repository leaves the old owner/name in the card URL, so a URL\n // mismatch still defers to the stable intake-stamped repository id.\n return item.metadata?.githubRepositoryId === repositoryId;\n}\n\nfunction canonicalSourceKey(kind: 'issue' | 'pull-request', itemNumber: number): string {\n return kind === 'issue' ? `github-issue:${itemNumber}` : `github-pr:${itemNumber}`;\n}\n\nfunction legacySourceKey(repositoryId: number, kind: 'issue' | 'pull-request', itemNumber: number): string {\n return `github:${repositoryId}:${kind}:${itemNumber}`;\n}\n\nfunction provenanceTarget(repositoryId: number, pullRequestNumber: number): string {\n return `factory-pr-provenance:${repositoryId}:${pullRequestNumber}`;\n}\n\nfunction workItemSource(item: WorkItemRow) {\n if (!item.externalSource) return 'manual' as const;\n return item.externalSource.type === 'pull-request' ? ('github-pr' as const) : ('github-issue' as const);\n}\n\nfunction workItemSourceKey(item: WorkItemRow): string | null {\n return item.externalSource?.externalId ?? null;\n}\n\n// Throws on a failed lookup: callers writing permanent state must retry, not\n// record the failure as distrust.\nexport async function trustedCollaborator(\n github: GithubRulesIntegration,\n input: { installationId: number; repository: string; login: string },\n): Promise<boolean> {\n const permission = await github.getRepositoryCollaboratorPermission(\n input.installationId,\n input.repository,\n input.login,\n );\n return permission !== undefined && TRUSTED_PERMISSIONS.has(permission);\n}\n\n// Terminal cards leave the reconcile loop below, so the ones that got there before author\n// trust was recorded have no other path to an answer.\nfunction authorAwaitingTrust(item: WorkItemRow, repository: ReconcileRepository): string | undefined {\n const metadata = item.metadata ?? {};\n if (typeof metadata.author !== 'string' || metadata.authorTrusted !== undefined) return undefined;\n const tracked = reconcilablePullRequestNumber(item, repository) ?? reconcilableIssueNumber(item, repository);\n if (tracked === undefined) return undefined;\n // A canonical key with no URL names no repository, and the pull request matcher takes it anyway.\n // Asking GitHub about the wrong repository of a multi-repository project would record a wrong answer.\n const unattributed =\n !item.externalSource?.url && /^github-(?:pr|issue):\\d+$/.test(item.externalSource?.externalId ?? '');\n if (unattributed && metadata.githubRepositoryId !== repository.id) return undefined;\n return metadata.author;\n}\n\nexport function sweepTrustLookup(\n github: GithubRulesIntegration,\n repository: ReconcileRepository,\n): (login: string) => Promise<boolean> {\n const cache = new Map<string, boolean>();\n return async login => {\n const cached = cache.get(login);\n if (cached !== undefined) return cached;\n const trusted = await trustedCollaborator(github, {\n installationId: repository.installationId,\n repository: repository.fullName,\n login,\n });\n cache.set(login, trusted);\n return trusted;\n };\n}\n\nasync function githubActor(\n github: GithubRulesIntegration,\n input: { installationId: number; repository: string; login: string; factoryAuthored: boolean },\n): Promise<FactoryRuleActor> {\n // The actor bit is recomputed on every event, so a failed lookup can read\n // untrusted for this one delivery instead of failing the ingest.\n const trusted = await trustedCollaborator(github, input).catch(() => false);\n return { type: 'github', login: input.login, trusted, factoryAuthored: input.factoryAuthored };\n}\n\ninterface FactoryPullRequestProvenanceData {\n kind: 'factory-pr-provenance';\n workItemId: string;\n}\n\nfunction pullRequestProvenance(\n data: Record<string, unknown> | undefined,\n factoryProjectId: string,\n): FactoryPullRequestProvenanceData | null {\n if (!data || data.kind !== 'factory-pr-provenance' || typeof data.workItemId !== 'string') return null;\n // Provenance proves which Factory *project's* run authored the PR. A row\n // written by a sibling project in the same org — or a legacy row without the\n // project stamp — fails closed here: honoring it would brand the PR\n // Factory-authored in a project that never touched it, and auto-start a\n // review that checks out and executes the PR there.\n if (data.factoryProjectId !== factoryProjectId) return null;\n return { kind: 'factory-pr-provenance', workItemId: data.workItemId };\n}\n\nexport interface GithubRulesIntegration {\n readonly rules: GithubEventRules;\n readonly slug?: string;\n /**\n * Factory's own GitHub login, used to ignore its own writes. Optional because\n * not every integration can name itself; when absent, self-recognition falls\n * back to the configured slug and, failing that, to content Factory stamps\n * itself (see `FACTORY_TRIAGE_COMMENT_MARKER`).\n */\n readonly identity?: GithubAppIdentity;\n getRepositoryCollaboratorPermission(\n installationId: number,\n repoFullName: string,\n username: string,\n ): Promise<GithubRepositoryPermission | undefined>;\n}\n\nexport interface GithubRulesOptions {\n github: GithubRulesIntegration;\n sourceControl: SourceControlStorageHandle;\n /** Integration-scoped storage; provenance rows are validated at read. */\n integrationStorage: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n storage: WorkItemsStorage;\n configVersion: string;\n boards: BoardRegistry;\n /**\n * Decides which installed board an issue lands on: a label route first, then the repository's\n * intake source binding. Absent means Work.\n */\n intake?: Pick<IntakeStorage, 'listLabelRoutes' | 'listBindings'>;\n}\n\n/** Identity under which label-driven relocations are recorded. */\nconst LABEL_ROUTE_USER_ID = 'factory-rule-dispatcher';\n\nfunction issueLabelChange(parsed: ParsedGithubWebhook): boolean {\n const action = string(parsed.payload.action);\n return parsed.event === 'issues' && (action === 'labeled' || action === 'unlabeled');\n}\n\n/**\n * Whether a login is Factory itself. Prefers the resolved identity, which is\n * observed from Factory's own writes, and falls back to the configured slug.\n */\nexport function isFactoryGithubLogin(\n github: Pick<GithubRulesIntegration, 'identity' | 'slug'>,\n login: string | null | undefined,\n): boolean {\n if (github.identity?.known) return github.identity.matches(login);\n const slug = github.slug?.trim();\n if (!slug || !login) return false;\n return login.toLowerCase() === `${slug.toLowerCase()}[bot]`;\n}\n\nexport class GithubRules {\n constructor(private readonly options: GithubRulesOptions) {}\n\n /**\n * Whether a login is Factory itself. Prefers the resolved identity, which is\n * observed from Factory's own writes, and falls back to the configured slug.\n * An unset slug must not silently answer \"not Factory\" — that is what\n * disabled every self-loop guard.\n */\n #isFactoryLogin(login: string | undefined): boolean {\n return isFactoryGithubLogin(this.options.github, login);\n }\n\n #factoryMentionTarget(): string | undefined {\n const identity = this.options.github.identity;\n if (identity?.known) return identity.login;\n const slug = this.options.github.slug?.trim();\n return slug ? `${slug.toLowerCase()}[bot]` : undefined;\n }\n\n /**\n * Board an issue lands on: the one its labels select under the project's label routes, else the\n * board the repository's intake source binding names. Only installed boards count. Undefined\n * leaves built-in routing (Work) in charge.\n */\n async #intakeTarget(\n project: ExternalRepositoryProjectTarget,\n repositoryName: string,\n labels: readonly string[],\n ): Promise<{ board: string; initialPhase: string } | undefined> {\n const intake = this.options.intake;\n if (!intake) return undefined;\n const { orgId, factoryProjectId } = project;\n if (labels.length > 0) {\n const routes = await intake.listLabelRoutes({ orgId, factoryProjectId, integrationId: 'github' });\n const route = resolveIntakeLabelRoute(routes, labels);\n const board = route ? this.options.boards.get(route.board) : undefined;\n if (board) return { board: board.id, initialPhase: board.initialPhase };\n }\n // GitHub bindings are keyed by the internal repository id; the slug is accepted too since the\n // bindings API stores whatever source id it is given.\n const slug = repositoryName.toLowerCase();\n const binding = (await intake.listBindings({ orgId, integrationId: 'github' })).find(\n candidate =>\n candidate.factoryProjectId === factoryProjectId &&\n (candidate.sourceId === project.projectRepository.repositoryId || candidate.sourceId.toLowerCase() === slug),\n );\n const board = binding?.board ? this.options.boards.get(binding.board) : undefined;\n return board ? { board: board.id, initialPhase: board.initialPhase } : undefined;\n }\n\n /**\n * `labeled` / `unlabeled` are not rule events: the labels only decide which board the card belongs\n * on, so the card is re-routed here and its label metadata refreshed. Cards a run owns, and\n * finished cards, stay where they are.\n */\n async #relocateLabeledIssue(\n parsed: ParsedGithubWebhook,\n repositoryId: number,\n repositoryName: string,\n project: ExternalRepositoryProjectTarget,\n ): Promise<{ status: 'ignored' | 'committed' }> {\n const issue = object(parsed.payload.issue);\n const issueNumber = issue?.pull_request === undefined ? number(issue?.number) : undefined;\n if (!issueNumber) return { status: 'ignored' };\n const found = await this.#relatedItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n issueNumber,\n undefined,\n undefined,\n null,\n );\n if (!found || found.externalSource?.type !== 'issue') return { status: 'ignored' };\n const labels = labelNames(issue?.labels);\n let item = found;\n let changed = false;\n if (!sameLabels(cardLabels(item), labels)) {\n let updated;\n try {\n updated = await this.options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: LABEL_ROUTE_USER_ID,\n patch: { metadata: { ...(item.metadata ?? {}), labels } },\n expectedRevision: item.revision,\n });\n } catch (error) {\n // The card changed under us (a run wrote it, or a concurrent delivery won). The next\n // delivery or reconcile pass carries the same labels, so drop this one instead of 5xx-ing.\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n return { status: 'ignored' };\n }\n if (!updated) return { status: 'ignored' };\n item = updated.item;\n changed = true;\n }\n const target = await this.#intakeTarget(project, repositoryName, labels);\n const outcome = await moveCardToBoard({\n workItems: this.options.storage,\n boardRegistry: this.options.boards,\n userId: LABEL_ROUTE_USER_ID,\n item,\n targetBoard: target?.board ?? 'work',\n });\n return { status: changed || outcome === 'moved' ? 'committed' : 'ignored' };\n }\n\n async ingest(parsed: ParsedGithubWebhook): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> {\n const event = eventName(parsed);\n const labelChange = issueLabelChange(parsed);\n const repository = object(parsed.payload.repository);\n const installationId = number(object(parsed.payload.installation)?.id);\n const repositoryId = number(repository?.id);\n const repositoryName = string(repository?.full_name);\n const login = string(object(parsed.payload.sender)?.login);\n if ((!event && !labelChange) || !installationId || !repositoryId || !repositoryName || !login) {\n return { status: 'ignored' };\n }\n\n const projects = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: String(installationId),\n repositoryExternalId: String(repositoryId),\n });\n if (projects.length === 0) return { status: 'ignored' };\n const results = [];\n for (const project of projects) {\n results.push(\n event\n ? await this.#ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, project)\n : await this.#relocateLabeledIssue(parsed, repositoryId, repositoryName, project),\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 async #ingestProject(\n parsed: ParsedGithubWebhook,\n event: FactoryGithubEventName,\n installationId: number,\n repositoryId: number,\n repositoryName: string,\n login: string,\n project: ExternalRepositoryProjectTarget,\n ): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> {\n const factoryProject = await this.options.projects.get({\n orgId: project.orgId,\n id: project.factoryProjectId,\n });\n if (!factoryProject) return { status: 'missing' };\n const issue = object(parsed.payload.issue);\n const issueComment = object(parsed.payload.comment);\n const changes = object(parsed.payload.changes);\n // A comment on a PR carries the PR under `issue` (with `pull_request` set)\n // and has no `pull_request` payload of its own. Read the PR from `issue` in\n // that case so provenance and `context.pullRequest` behave as they do for\n // every other PR event, and so the number is never treated as an issue's.\n const commentOnPullRequest = object(parsed.payload.issue)?.pull_request !== undefined;\n const pullRequest = object(parsed.payload.pull_request) ?? (commentOnPullRequest ? issue : undefined);\n const issueNumber = commentOnPullRequest ? undefined : number(issue?.number);\n const pullRequestNumber = number(pullRequest?.number);\n const provenance = pullRequestNumber\n ? pullRequestProvenance(\n (\n await this.options.integrationStorage.subscriptions.listByTarget(\n provenanceTarget(repositoryId, pullRequestNumber),\n { status: 'active' },\n )\n ).find(\n subscription =>\n subscription.orgId === project.orgId && subscription.data?.factoryProjectId === project.factoryProjectId,\n )?.data,\n project.factoryProjectId,\n )\n : null;\n // Re-review events target the PR's own Review card, not the Work item that\n // provenance would otherwise bind the event to. For review_requested the\n // sender is whoever clicked re-request, so a Factory-authored PR must not\n // brand a human requester as factory-authored.\n const reviewRequested = event === 'pullRequestReviewRequested';\n const reviewCommand =\n event === 'pullRequestCommentCreated'\n ? parseFactoryReviewCommand(string(issueComment?.body), this.#factoryMentionTarget())\n : undefined;\n const reviewEntryRequested = reviewRequested || reviewCommand !== undefined;\n // Provenance proves the *pull request* came from Factory, which is not the\n // same as the sender of this event. For events where the sender is whoever\n // reacted to the PR — re-requesting review, commenting, submitting a review\n // — branding them from provenance would mark every human and every review\n // bot as Factory. Only the app login identifies Factory for those.\n const senderIsResponder =\n reviewRequested || event === 'pullRequestCommentCreated' || event === 'pullRequestReviewSubmitted';\n const reReviewEvent = reviewEntryRequested || event === 'pullRequestUpdated';\n const requestedReviewer = string(object(parsed.payload.requested_reviewer)?.login);\n const pullRequestAuthor = string(object(pullRequest?.user)?.login);\n const pullRequestFactoryAuthored = provenance !== null || this.#isFactoryLogin(pullRequestAuthor);\n const resolvedItem = await this.#relatedItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n issueNumber,\n pullRequestNumber,\n string(object(pullRequest?.head)?.ref),\n reReviewEvent ? null : provenance,\n senderIsResponder && !reviewEntryRequested,\n );\n // A review-entry request must never treat a branch-matched authoring Work\n // card as the PR's Review card. A missing Review card is materialized below.\n const relatedItem =\n reviewEntryRequested && resolvedItem?.externalSource?.type !== 'pull-request' ? undefined : resolvedItem;\n const intake = issueNumber\n ? await this.#intakeTarget(project, repositoryName, labelNames(issue?.labels))\n : undefined;\n const actor = await githubActor(this.options.github, {\n installationId,\n repository: repositoryName,\n login,\n factoryAuthored: (!senderIsResponder && provenance !== null) || this.#isFactoryLogin(login),\n });\n // A marked handoff comment is ignored only when Factory authored it: a\n // human may quote the marker to add an investigation lead, and that must\n // still retrigger triage. Recognising the author is therefore the whole\n // guard — when identity cannot be resolved this fails open and Factory's\n // own handoff cancels the run that wrote it.\n if (\n actor.type === 'github' &&\n actor.factoryAuthored &&\n (event === 'issueCommentCreated' || event === 'issueCommentEdited') &&\n string(issueComment?.body)?.includes(FACTORY_TRIAGE_COMMENT_MARKER)\n ) {\n return { status: 'ignored' };\n }\n // One delivery can concern two cards — a merged pull request settles both\n // its own Review card and the Work item that authored it — and every\n // decision a rule returns is committed against a single item, at that\n // item's revision. So the evaluation, not the decision, is what fans out:\n // the rule runs once per bound item, each with its own ingress identity.\n const evaluate = async (\n item: WorkItemRow | undefined,\n ingressIdentity: string,\n /** Set on the evaluation that files the pull request's own Review card. */\n pullRequestIntake = false,\n attempt = 1,\n ): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> => {\n const context: FactoryGithubRuleContext = {\n tenant: { orgId: project.orgId, projectId: project.factoryProjectId },\n actor,\n ingress: { type: 'github', id: ingressIdentity },\n cause: `github.${event}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n ...(item\n ? {\n item: {\n id: item.id,\n source: workItemSource(item),\n sourceKey: workItemSourceKey(item),\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: item.stages,\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board: boardForWorkItem(item),\n itemRevision: item.revision,\n ...withBoardClosure(this.options.boards, item),\n ...rulePhaseField(this.options.boards, item),\n }\n : {}),\n ...(intake ? { intake } : {}),\n ...(pullRequestIntake ? { pullRequestIntake: true } : {}),\n event,\n deliveryId: parsed.deliveryId,\n factory: { createdAt: factoryProject.createdAt.toISOString() },\n repository: { id: repositoryId, fullName: repositoryName, installationId },\n ...(issueNumber && string(issue?.title) && string(issue?.html_url)\n ? {\n issue: {\n number: issueNumber,\n title: string(issue?.title)!,\n url: string(issue?.html_url)!,\n ...(string(issue?.created_at) ? { createdAt: string(issue?.created_at) } : {}),\n ...(string(issue?.updated_at) ? { updatedAt: string(issue?.updated_at) } : {}),\n assignees: actorLogins(issue?.assignees),\n labels: labelNames(issue?.labels),\n ...(string(issue?.state) === 'closed' || string(issue?.state) === 'open'\n ? { state: string(issue?.state) as 'open' | 'closed' }\n : {}),\n ...(string(issue?.state_reason) ? { stateReason: string(issue?.state_reason) } : {}),\n },\n }\n : {}),\n ...(event === 'issueEdited'\n ? { issueChange: { title: Boolean(object(changes?.title)), body: Boolean(object(changes?.body)) } }\n : {}),\n ...(number(issueComment?.id)\n ? {\n issueComment: {\n id: number(issueComment?.id)!,\n ...(string(issueComment?.body) ? { body: string(issueComment?.body) } : {}),\n ...(string(issueComment?.html_url) ? { url: string(issueComment?.html_url) } : {}),\n ...(string(object(issueComment?.user)?.login)\n ? { author: string(object(issueComment?.user)?.login) }\n : {}),\n ...(string(object(issueComment?.user)?.type)\n ? { authorType: string(object(issueComment?.user)?.type) }\n : {}),\n ...(string(issueComment?.created_at) ? { createdAt: string(issueComment?.created_at) } : {}),\n ...(string(issueComment?.updated_at) ? { updatedAt: string(issueComment?.updated_at) } : {}),\n },\n }\n : {}),\n ...(pullRequestNumber && string(pullRequest?.title) && string(pullRequest?.html_url)\n ? {\n pullRequest: {\n number: pullRequestNumber,\n title: string(pullRequest?.title)!,\n url: string(pullRequest?.html_url)!,\n ...(string(pullRequest?.created_at) ? { createdAt: string(pullRequest?.created_at) } : {}),\n state: string(pullRequest?.state) === 'closed' ? ('closed' as const) : ('open' as const),\n draft: boolean(pullRequest?.draft) ?? false,\n merged: boolean(pullRequest?.merged) ?? false,\n assignees: actorLogins(pullRequest?.assignees),\n requestedReviewers: actorLogins(pullRequest?.requested_reviewers),\n labels: labelNames(pullRequest?.labels),\n ...(pullRequestAuthor ? { author: pullRequestAuthor } : {}),\n factoryAuthored: pullRequestFactoryAuthored,\n headBranch: string(object(pullRequest?.head)?.ref) ?? '',\n baseBranch: string(object(pullRequest?.base)?.ref) ?? '',\n },\n }\n : {}),\n ...(reviewRequested && requestedReviewer\n ? {\n reviewRequest: {\n reviewer: requestedReviewer,\n factoryReviewer: this.#isFactoryLogin(requestedReviewer),\n },\n }\n : {}),\n ...(reviewCommand ? { reviewCommand } : {}),\n ...(object(parsed.payload.review)\n ? {\n review: {\n id: number(object(parsed.payload.review)?.id) ?? 0,\n state: string(object(parsed.payload.review)?.state) ?? 'unknown',\n url: string(object(parsed.payload.review)?.html_url) ?? '',\n ...(string(object(object(parsed.payload.review)?.user)?.login)\n ? { author: string(object(object(parsed.payload.review)?.user)?.login) }\n : {}),\n ...(string(object(parsed.payload.review)?.body)\n ? { body: string(object(parsed.payload.review)?.body) }\n : {}),\n },\n }\n : {}),\n };\n\n const rule = selectEventRule(\n this.options.github.rules,\n boardEventHandlers(this.options.boards, eventBoardId(context), 'github'),\n event,\n );\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(entry, this.options.boards, item ? boardForWorkItem(item) : undefined);\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 GitHub rule failed.',\n };\n }\n\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: project.orgId,\n factoryProjectId: project.factoryProjectId,\n workItemId: item?.id ?? null,\n ingress: { identity: ingressIdentity, triggerType: `github.${event}` },\n configVersion: this.options.configVersion,\n expectedRevision: item?.revision ?? null,\n actor: { ...actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n if (committed.status === 'stale') {\n // The item was written concurrently. Re-read it and run the rule again\n // against the fresh revision; nothing was persisted for this attempt.\n if (attempt >= MAX_STALE_RULE_ATTEMPTS) {\n throw new Error(`Factory GitHub rule evaluation for ${ingressIdentity} kept losing revision races.`);\n }\n const fresh = item ? await this.options.storage.get({ orgId: project.orgId, id: item.id }) : null;\n if (item && !fresh) return { status: 'missing' };\n return evaluate(fresh ?? undefined, ingressIdentity, pullRequestIntake, attempt + 1);\n }\n return { status: committed.status };\n };\n\n const deliveryIdentity = `${installationId}:${parsed.deliveryId}`;\n // A pull request opening concerns two cards: its own Review card, which the\n // arrival rule files — committed against the Work item that authored the\n // pull request when one exists, because that binding is what links the two\n // — and that authoring item, which is now out for review. The arrival is\n // the delivery's own evaluation; the item's is a second one, under an\n // identity suffixed with its id, because ingress identities are the replay\n // key and reusing the delivery's own would drop it as a duplicate.\n const pullRequestOpened = event === 'pullRequestOpened';\n const primary = await evaluate(relatedItem, deliveryIdentity, pullRequestOpened);\n // A closed pull request is the one *other* event both linked cards need:\n // the Review card has to close, and the Work item that wrote the code has\n // to finish (on merge) or at least stop recording it as open. Resolution binds the delivery to\n // whichever card it matched first, so evaluate the other one too.\n const linked =\n (event === 'pullRequestMerged' || event === 'pullRequestClosed') && relatedItem && pullRequestNumber\n ? await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n relatedItem,\n )\n : undefined;\n // Moving the card to Done does not touch its pull request fields, so without\n // this the card reads \"open\" until the next sweep and still offers Re-review.\n // Stamped after the evaluations, which commit at the revision they read.\n const stampClosed = async (companionStatus?: string) => {\n if (!pullRequestNumber) return;\n // The authoring Work item records which pull request it has out, so an\n // agent cannot close the work while that pull request is still open.\n if (pullRequestOpened) {\n // Mirrors the out-for-review rule's trust gate. A replayed opened\n // delivery only restores the mark (after a failed write) while the\n // pull request's Review card still records it open, so a redelivery\n // after the close cannot block the Work item again.\n const trusted = (actor.type === 'github' && actor.trusted) || pullRequestFactoryAuthored;\n const reviewCard =\n authoringItem && trusted\n ? await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n authoringItem,\n )\n : undefined;\n const stillOpen =\n companionStatus === 'replayed'\n ? reviewCard !== undefined && reviewCard.metadata?.state !== 'closed'\n : reviewCard?.metadata?.state !== 'closed';\n if (authoringItem && trusted && stillOpen) {\n // The head comes along so a head recorded for an earlier pull\n // request cannot mark this one's verdict outdated. A replayed opening\n // of the pull request already recorded keeps the head its pushes left.\n const rebinding = authoringItem.metadata?.openPullRequestNumber !== pullRequestNumber;\n const head = rebinding\n ? {\n pullRequestHeadSha: string(object(pullRequest?.head)?.sha)?.toLowerCase() ?? null,\n pullRequestHeadAt: timestamp(pullRequest?.updated_at) ?? null,\n }\n : {};\n await this.options.storage.update({\n orgId: authoringItem.orgId,\n id: authoringItem.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { openPullRequestNumber: pullRequestNumber, ...head } },\n });\n }\n return;\n }\n if (event !== 'pullRequestMerged' && event !== 'pullRequestClosed') return;\n for (const card of [relatedItem, linked]) {\n if (!card) continue;\n if (card.externalSource?.type !== 'pull-request') {\n // A merge settles the review, so the authoring Work item's mirrored\n // \"request changes\" no longer stops it from being closed.\n // Only the pull request the card has out settles it; an older one\n // merging must not clear a verdict that belongs to its successor.\n const recorded = card.metadata?.openPullRequestNumber;\n const ownsCard = typeof recorded !== 'number' || recorded === pullRequestNumber;\n const settled = {\n ...(ownsCard && event === 'pullRequestMerged' && typeof card.metadata?.reviewVerdict === 'string'\n ? { reviewVerdict: null }\n : {}),\n ...(recorded === pullRequestNumber ? { openPullRequestNumber: null } : {}),\n };\n if (Object.keys(settled).length > 0) {\n await this.options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: settled },\n });\n }\n continue;\n }\n await this.options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { state: 'closed', merged: event === 'pullRequestMerged' } },\n });\n }\n };\n const authoringItem =\n linked === undefined &&\n pullRequestOpened &&\n relatedItem !== undefined &&\n relatedItem.externalSource?.type !== 'pull-request'\n ? relatedItem\n : undefined;\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 stampHead = async () => {\n const headSha = string(object(pullRequest?.head)?.sha)?.toLowerCase();\n if (event !== 'pullRequestUpdated' || !headSha || !pullRequestNumber) return;\n const headAt = timestamp(pullRequest?.updated_at);\n if (relatedItem?.externalSource?.type !== 'pull-request') return;\n const parent = await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n relatedItem,\n );\n // The authoring Work item mirrors the verdict of the pull 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: relatedItem.orgId,\n id: relatedItem.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: pullRequestNumber,\n });\n }\n };\n if (linked === undefined && authoringItem === undefined) {\n await stampClosed();\n await stampHead();\n return primary;\n }\n const companion = linked ?? authoringItem;\n const secondary = await evaluate(companion, `${deliveryIdentity}:${companion?.id ?? 'pull-request'}`);\n await stampClosed(secondary.status);\n for (const status of ['committed', 'replayed'] as const) {\n if (primary.status === status || secondary.status === status) return { status };\n }\n return primary;\n }\n\n /**\n * The other card a closed pull request concerns, joined through the Review\n * card's `parentWorkItemId` — the link `upsertLinkedWorkItem` records when the\n * pull request is opened, and therefore an exact join that needs no branch\n * heuristics. Returns nothing when the pull request has only one card, which\n * is every pull request Factory did not open from a work item's session.\n */\n async #linkedClosureItem(\n orgId: string,\n projectId: string,\n repositoryId: number,\n repositoryFullName: string,\n pullRequestNumber: number,\n resolved: WorkItemRow,\n ): Promise<WorkItemRow | undefined> {\n const items = await this.options.storage.list({ orgId, factoryProjectId: projectId });\n const linked =\n resolved.externalSource?.type === 'pull-request'\n ? // Bound to the pull request's own Review card: follow the recorded\n // link back to the work item that authored it.\n items.find(item => item.id === resolved.parentWorkItemId)\n : // Bound to the work item (provenance): find the Review card this pull\n // request opened, and only when it names this item as its parent — a\n // card for the same number in another repository is not this one's.\n items.find(\n item =>\n item.parentWorkItemId === resolved.id &&\n (item.externalSource?.externalId === canonicalSourceKey('pull-request', pullRequestNumber) ||\n item.externalSource?.externalId === legacySourceKey(repositoryId, 'pull-request', pullRequestNumber)) &&\n cardBelongsToRepository(item, repositoryId, repositoryFullName),\n );\n return linked?.id === resolved.id ? undefined : linked;\n }\n\n async #relatedItem(\n orgId: string,\n projectId: string,\n repositoryId: number,\n repositoryFullName: string,\n issueNumber: number | undefined,\n pullRequestNumber: number | undefined,\n pullRequestHeadBranch: string | undefined,\n provenance: FactoryPullRequestProvenanceData | null,\n preferAuthoringItem = false,\n ): Promise<WorkItemRow | undefined> {\n const items = await this.options.storage.list({ orgId, factoryProjectId: projectId });\n const resolved = this.#resolveItem(\n items,\n repositoryId,\n repositoryFullName,\n issueNumber,\n pullRequestNumber,\n pullRequestHeadBranch,\n provenance,\n );\n // Feedback on a pull request has to reach the item that *wrote* the code.\n // Provenance normally lands it there directly, but when provenance is\n // missing the PR-number lookup wins and returns the PR's own Review card\n // instead — a board the feedback rules deliberately refuse to act on, so\n // the wake is silently dropped. The linked card records its author in\n // `parentWorkItemId`, so follow that link back rather than relaxing the\n // guard, which would let a Review card react to its own posted review.\n if (preferAuthoringItem && resolved?.externalSource?.type === 'pull-request' && resolved.parentWorkItemId) {\n return items.find(item => item.id === resolved.parentWorkItemId) ?? resolved;\n }\n return resolved;\n }\n\n #resolveItem(\n items: WorkItemRow[],\n repositoryId: number,\n repositoryFullName: string,\n issueNumber: number | undefined,\n pullRequestNumber: number | undefined,\n pullRequestHeadBranch: string | undefined,\n provenance: FactoryPullRequestProvenanceData | null,\n ): WorkItemRow | undefined {\n if (provenance) return items.find(item => item.id === provenance.workItemId);\n if (issueNumber) {\n return (\n items.find(\n item =>\n item.externalSource?.externalId === canonicalSourceKey('issue', issueNumber) &&\n cardBelongsToRepository(item, repositoryId, repositoryFullName),\n ) ?? items.find(item => item.externalSource?.externalId === legacySourceKey(repositoryId, 'issue', issueNumber))\n );\n }\n if (pullRequestNumber) {\n return (\n items.find(\n item =>\n item.externalSource?.externalId === canonicalSourceKey('pull-request', pullRequestNumber) &&\n cardBelongsToRepository(item, repositoryId, repositoryFullName),\n ) ??\n items.find(\n item => item.externalSource?.externalId === legacySourceKey(repositoryId, 'pull-request', pullRequestNumber),\n ) ??\n // Provenance fallback: a PR pushed from a work item's session branch\n // belongs to that item even when no gh-pr-create provenance was\n // recorded (session predating state seeding, or the PR was opened\n // outside the tracked tool call). Session branches are per-item\n // (`factory/issue-N`), so a head-branch match is unambiguous.\n (pullRequestHeadBranch\n ? items.find(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === pullRequestHeadBranch),\n )\n : undefined)\n );\n }\n return undefined;\n }\n}\n\nexport interface ReconcilePullRequestState {\n title: string;\n url: string;\n state: 'open' | 'closed';\n draft: boolean;\n merged: boolean;\n assignees?: string[];\n requestedReviewers?: string[];\n labels?: string[];\n headBranch: string;\n baseBranch: string;\n author?: string;\n createdAt?: string;\n mergedBy?: string;\n}\n\nexport interface PolledPullRequestState extends ReconcilePullRequestState {\n createdAt: string;\n}\n\nexport type GithubPullRequestFetcher = (input: {\n installationId: number;\n repository: string;\n number: number;\n}) => Promise<ReconcilePullRequestState | undefined>;\n\nexport interface ReconcileIssueState {\n title: string;\n url: string;\n state: 'open' | 'closed';\n /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */\n stateReason?: string;\n assignees?: string[];\n labels?: string[];\n author?: string;\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport type GithubIssueFetcher = (input: {\n installationId: number;\n repository: string;\n number: number;\n}) => Promise<ReconcileIssueState | undefined>;\n\n/** Lists a repository's open issues (pull requests excluded) for missed-open discovery. */\nexport type GithubOpenIssueLister = (input: {\n installationId: number;\n repository: string;\n}) => Promise<Array<ReconcileIssueState & { number: number }>>;\n\nexport interface ReconcileRepository {\n id: number;\n fullName: string;\n installationId: number;\n}\n\nexport interface ReconcileSweepSummary {\n /** Factory-configured repositories included in the sweep. */\n repositories: number;\n /** PRs whose live state was fetched from GitHub. */\n checked: number;\n /** Missed merges replayed through the rules ingress. */\n merged: number;\n /** Missed closes-without-merge replayed through the rules ingress. */\n closed: number;\n /** PRs/issues (or whole repositories) skipped because of an error. */\n failed: number;\n /** Error samples with context, capped at {@link RECONCILE_ERROR_SAMPLE_LIMIT}. */\n errors: Array<{ repository: string; pullRequestNumber?: number; issueNumber?: number; error: string }>;\n}\n\nexport type GithubPullRequestReconciler = (repositories: ReconcileRepository[]) => Promise<ReconcileSweepSummary>;\n\nexport const RECONCILE_ERROR_SAMPLE_LIMIT = 5;\n\nexport function sameStrings(left: unknown, right: string[] | undefined): boolean {\n if (right === undefined) return true;\n if (!Array.isArray(left)) return false;\n const leftValues = new Set(left.flatMap(value => (typeof value === 'string' ? [value] : [])));\n const rightValues = new Set(right);\n return leftValues.size === rightValues.size && [...leftValues].every(value => rightValues.has(value));\n}\n\n/**\n * Extracts the PR number a work item tracks, but only when the item belongs\n * to the given repository. Card URLs pin the repository unambiguously; the\n * legacy source key embeds the repository id. Canonical keys (`github-pr:N`)\n * carry no repository, so they are only trusted when the item has no URL —\n * a project mapped to multiple repositories must not reconcile one repo's\n * card against another repo's PR number.\n */\nfunction reconcilablePullRequestNumber(item: WorkItemRow, repository: ReconcileRepository): number | undefined {\n if (item.externalSource?.type !== 'pull-request') return undefined;\n const url = item.externalSource.url;\n if (url) {\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/pull\\/(\\d+)(?:[/?#]|$)/.exec(url);\n if (!match) return undefined;\n return match[1] === repository.fullName ? Number(match[2]) : undefined;\n }\n const externalId = item.externalSource.externalId;\n const legacy = /^github:(\\d+):pull-request:(\\d+)$/.exec(externalId);\n if (legacy) return Number(legacy[1]) === repository.id ? Number(legacy[2]) : undefined;\n const canonical = /^github-pr:(\\d+)$/.exec(externalId);\n return canonical ? Number(canonical[1]) : undefined;\n}\n\n/**\n * Extracts the issue number a work item tracks, but only when the item\n * belongs to the given repository. Stricter than\n * {@link reconcilablePullRequestNumber}: a canonical key with no URL is only\n * trusted when the intake-stamped `githubRepositoryId` confirms the\n * repository, because the sweep initiates closes on its own.\n */\nexport function reconcilableIssueNumber(item: WorkItemRow, repository: ReconcileRepository): number | undefined {\n if (item.externalSource?.type !== 'issue') return undefined;\n const url = item.externalSource.url;\n if (url) {\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/issues\\/(\\d+)(?:[/?#]|$)/.exec(url);\n if (!match) return undefined;\n // A renamed repository leaves the old owner/name in the card URL, so a\n // URL mismatch still defers to the stable intake-stamped repository id.\n const belongs = match[1] === repository.fullName || item.metadata?.githubRepositoryId === repository.id;\n return belongs ? Number(match[2]) : undefined;\n }\n const externalId = item.externalSource.externalId;\n const legacy = /^github:(\\d+):issue:(\\d+)$/.exec(externalId);\n if (legacy) return Number(legacy[1]) === repository.id ? Number(legacy[2]) : undefined;\n const canonical = /^github-issue:(\\d+)$/.exec(externalId);\n if (!canonical) return undefined;\n // Canonical keys carry no repository; only the intake-stamped repository id\n // can attribute a URL-less card, and guessing would let a multi-repo\n // project close repo B's card because repo A's same-numbered issue closed.\n return item.metadata?.githubRepositoryId === repository.id ? Number(canonical[1]) : undefined;\n}\n\nexport function reconciledIssueClosedEvent(\n repository: ReconcileRepository,\n issueNumber: number,\n state: ReconcileIssueState,\n): ParsedGithubWebhook {\n return {\n event: 'issues',\n // Stable per (repository, issue): the ingress dedupe makes repeat\n // reconcile cycles replay instead of re-committing decisions.\n deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:closed`,\n payload: {\n action: 'closed',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.author ?? 'github' },\n issue: {\n number: issueNumber,\n title: state.title,\n html_url: state.url,\n state: 'closed',\n ...(state.stateReason ? { state_reason: state.stateReason } : {}),\n ...(state.createdAt ? { created_at: state.createdAt } : {}),\n ...(state.updatedAt ? { updated_at: state.updatedAt } : {}),\n assignees: githubUsers(state.assignees),\n },\n },\n };\n}\n\n/**\n * The label-drift replay: an open issue whose labels changed without a\n * `labeled`/`unlabeled` webhook ever arriving (Factory was down, the delivery\n * was lost, or the label was applied by something other than a delivery the\n * webhook saw). Synthesized as an `opened` delivery so the deployment's\n * `issueOpened` rule decides placement, exactly as it does at arrival — one\n * source of placement policy for both.\n */\nexport function reconciledIssueRelabeledEvent(\n repository: ReconcileRepository,\n issueNumber: number,\n state: ReconcileIssueState,\n): ParsedGithubWebhook {\n const labels = state.labels ?? [];\n // GitHub updates `updated_at` for a label change. Including it makes retries\n // of one observed issue version idempotent while allowing A → B → A to replay\n // its final A placement as a new version. Synthetic callers without it retain\n // the legacy label-only identity.\n const digest = createHash('sha256')\n .update([...labels].sort().join('\\n'))\n .digest('hex')\n .slice(0, 16);\n const version = state.updatedAt ? `:${createHash('sha256').update(state.updatedAt).digest('hex').slice(0, 16)}` : '';\n return {\n event: 'issues',\n deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:relabeled:${digest}${version}`,\n payload: {\n // `opened` is the event that carries an issue's labels through the rules;\n // an already-filed card is re-placed by the decision, not re-materialized.\n action: 'opened',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.author ?? 'github' },\n issue: {\n number: issueNumber,\n title: state.title,\n html_url: state.url,\n state: 'open',\n ...(state.createdAt ? { created_at: state.createdAt } : {}),\n ...(state.updatedAt ? { updated_at: state.updatedAt } : {}),\n assignees: githubUsers(state.assignees),\n labels: githubLabels(labels),\n },\n },\n };\n}\n\n/**\n * The missed-open replay: an open issue with no Work card, because its\n * `opened` delivery never reached the rules ingress. Synthesized as the same\n * `opened` delivery so the deployment's `issueOpened` rule decides whether and\n * where it lands, exactly as it would have at arrival. The delivery id is\n * stable per issue so repeat sweeps dedupe at the ingress.\n */\nexport function reconciledIssueOpenedEvent(\n repository: ReconcileRepository,\n issueNumber: number,\n state: ReconcileIssueState,\n): ParsedGithubWebhook {\n const event = reconciledIssueRelabeledEvent(repository, issueNumber, state);\n return { ...event, deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:opened` };\n}\n\nexport function polledPullRequestEvent(\n repository: ReconcileRepository,\n pullRequestNumber: number,\n state: PolledPullRequestState,\n): ParsedGithubWebhook {\n return {\n event: 'pull_request',\n deliveryId: `poll:${repository.id}:pull-request:${pullRequestNumber}:${state.createdAt}`,\n payload: {\n action: 'opened',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.author ?? '__unknown__' },\n pull_request: pullRequestPayload(pullRequestNumber, state),\n },\n };\n}\n\nexport function reconciledClosedEvent(\n repository: ReconcileRepository,\n pullRequestNumber: number,\n state: ReconcilePullRequestState,\n): ParsedGithubWebhook {\n const outcome = state.merged ? 'merged' : 'closed';\n return {\n event: 'pull_request',\n // Stable per (repository, PR, outcome): the ingress dedupe makes repeat\n // reconcile cycles replay instead of re-committing decisions.\n deliveryId: `reconcile:${repository.id}:pull-request:${pullRequestNumber}:${outcome}`,\n payload: {\n action: 'closed',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.mergedBy ?? 'github' },\n pull_request: pullRequestPayload(pullRequestNumber, { ...state, state: 'closed' }),\n },\n };\n}\n\nfunction pullRequestPayload(pullRequestNumber: number, state: ReconcilePullRequestState): Record<string, unknown> {\n const payload: Record<string, unknown> = {\n number: pullRequestNumber,\n title: state.title,\n html_url: state.url,\n state: state.state,\n draft: state.draft,\n merged: state.merged,\n assignees: githubUsers(state.assignees),\n requested_reviewers: githubUsers(state.requestedReviewers),\n labels: githubLabels(state.labels),\n head: { ref: state.headBranch },\n base: { ref: state.baseBranch },\n };\n if (state.createdAt) payload.created_at = state.createdAt;\n if (state.author) payload.user = { login: state.author };\n return payload;\n}\n\nfunction githubUsers(logins: readonly string[] = []): Array<{ login: string }> {\n return logins.map(login => ({ login }));\n}\n\nfunction githubLabels(names: readonly string[] = []): Array<{ name: string }> {\n return names.map(name => ({ name }));\n}\n\nfunction reconciledPullRequestMetadata(\n state: ReconcilePullRequestState,\n reconciliation: 'clear' | 'settled',\n authorTrusted?: boolean,\n): Record<string, unknown> {\n return {\n state: state.state,\n draft: state.draft,\n merged: state.merged,\n ...(state.author ? { author: state.author } : {}),\n ...(state.assignees ? { assignees: state.assignees } : {}),\n ...(state.requestedReviewers ? { requestedReviewers: state.requestedReviewers } : {}),\n ...(state.labels ? { labels: state.labels } : {}),\n ...(authorTrusted === undefined ? {} : { authorTrusted }),\n [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]:\n reconciliation === 'settled' ? (state.merged ? 'merged' : 'closed') : null,\n };\n}\n\nfunction reconciledPullRequestOutcome(metadata: Record<string, unknown>): 'merged' | 'closed' | undefined {\n if (metadata.state !== 'closed' || typeof metadata.merged !== 'boolean') return undefined;\n return metadata.merged ? 'merged' : 'closed';\n}\n\n/**\n * The webhook handler retires subscriptions itself; the sweep replays only the\n * rules ingress, so without this the thread's PR chip and the workspace row\n * stay `open` forever on a deployment GitHub cannot reach.\n */\nasync function retireReconciledSubscriptions(\n storage: IntegrationStorageHandle,\n repository: ReconcileRepository,\n pullRequestNumber: number,\n merged: boolean,\n): Promise<void> {\n const target = changeRequestTargetKey({\n installationExternalId: String(repository.installationId),\n repositoryExternalId: String(repository.id),\n changeRequestId: String(pullRequestNumber),\n });\n const rows = await storage.subscriptions.listByTarget(target);\n await Promise.all(\n rows\n .filter(row => row.status === 'open')\n .map(row => storage.subscriptions.updateStatus(row.id, merged ? 'merged' : 'closed')),\n );\n}\n\n/**\n * State-based safety net for merge signals: webhooks and event-log tailing\n * can miss a merge (cursor gaps, downtime, terminally failed decisions), so\n * this sweep compares still-open PR cards against actual GitHub state and\n * replays the merge through the normal rules ingress when they disagree.\n */\nexport function createGithubPullRequestReconciler(\n options: GithubRulesOptions,\n fetchPullRequest: GithubPullRequestFetcher,\n): GithubPullRequestReconciler {\n const rules = new GithubRules(options);\n return async repositories => {\n const summary: ReconcileSweepSummary = {\n repositories: 0,\n checked: 0,\n merged: 0,\n closed: 0,\n failed: 0,\n errors: [],\n };\n const recordFailure = (repository: ReconcileRepository, error: unknown, pullRequestNumber?: number) => {\n summary.failed += 1;\n if (summary.errors.length < RECONCILE_ERROR_SAMPLE_LIMIT) {\n summary.errors.push({\n repository: repository.fullName,\n ...(pullRequestNumber === undefined ? {} : { pullRequestNumber }),\n error: error instanceof Error ? error.message : String(error),\n });\n }\n };\n // An installation can expose hundreds of repositories; only the ones\n // actually linked to a factory project can have cards to reconcile, so\n // scope the sweep to those up front instead of probing each repository.\n const configured = new Set(\n (await options.sourceControl.projectRepositories.listConfiguredExternalKeys()).map(\n key => `${key.installationExternalId}\\u0000${key.repositoryExternalId}`,\n ),\n );\n const scoped = repositories.filter(repository =>\n configured.has(`${repository.installationId}\\u0000${repository.id}`),\n );\n summary.repositories = scoped.length;\n for (const repository of scoped) {\n // One broken repository (or a failing token exchange for its\n // installation) must not abort the sweep for the others.\n let cardsByNumber: Map<number, WorkItemRow[]>;\n let unanswered: Array<{ item: WorkItemRow; author: string }>;\n try {\n const projects = await options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: String(repository.installationId),\n repositoryExternalId: String(repository.id),\n });\n if (projects.length === 0) continue;\n cardsByNumber = new Map<number, WorkItemRow[]>();\n unanswered = [];\n for (const project of projects) {\n const items = await options.storage.list({\n orgId: project.orgId,\n factoryProjectId: project.factoryProjectId,\n });\n for (const item of items) {\n const unansweredAuthor = authorAwaitingTrust(item, repository);\n if (unansweredAuthor) unanswered.push({ item, author: unansweredAuthor });\n const pullRequestNumber = reconcilablePullRequestNumber(item, repository);\n if (!pullRequestNumber) continue;\n const metadata = item.metadata ?? {};\n const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];\n const reconciledOutcome = reconciledPullRequestOutcome(metadata);\n if (\n workItemPhaseSemantics(options.boards, item)?.kind === 'terminal' &&\n reconciledOutcome !== undefined &&\n reconciliation === reconciledOutcome\n ) {\n continue;\n }\n const cards = cardsByNumber.get(pullRequestNumber) ?? [];\n cards.push(item);\n cardsByNumber.set(pullRequestNumber, cards);\n }\n }\n } catch (error) {\n recordFailure(repository, error);\n continue;\n }\n const authorTrust = sweepTrustLookup(options.github, repository);\n for (const { item, author } of unanswered) {\n try {\n await options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { authorTrusted: await authorTrust(author) } },\n });\n } catch (error) {\n recordFailure(repository, error);\n }\n }\n for (const [pullRequestNumber, cards] of cardsByNumber) {\n try {\n const state = await fetchPullRequest({\n installationId: repository.installationId,\n repository: repository.fullName,\n number: pullRequestNumber,\n });\n summary.checked += 1;\n if (!state) continue;\n // Re-stamped on every sweep so revoked write access reads untrusted\n // within one cycle; a failed lookup keeps the last stamp and retries.\n let authorTrusted: boolean | undefined;\n if (state.author !== undefined) {\n try {\n authorTrusted = await authorTrust(state.author);\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n for (const card of cards) {\n if (state.state === 'closed') continue;\n const metadata = card.metadata ?? {};\n const statusChanged =\n metadata.state !== state.state || metadata.draft !== state.draft || metadata.merged !== state.merged;\n const authorChanged = state.author !== undefined && metadata.author !== state.author;\n const assigneesChanged = !sameStrings(metadata.assignees, state.assignees);\n const reviewersChanged = !sameStrings(metadata.requestedReviewers, state.requestedReviewers);\n const labelsChanged = !sameStrings(metadata.labels, state.labels);\n const trustStale = authorTrusted !== undefined && metadata.authorTrusted !== authorTrusted;\n const metadataChanged =\n statusChanged || authorChanged || assigneesChanged || reviewersChanged || labelsChanged || trustStale;\n const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];\n if (!metadataChanged && reconciliation !== 'merged' && reconciliation !== 'closed') continue;\n try {\n await options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: {\n metadata: reconciledPullRequestMetadata(state, 'clear', trustStale ? authorTrusted : undefined),\n },\n });\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n if (state.state !== 'closed') continue;\n const cleanupFailures = new Set<string>();\n for (const card of cards) {\n if (workItemPhaseSemantics(options.boards, card)?.kind !== 'terminal') continue;\n try {\n await options.storage.supersedeDecisionsForWorkItem({\n orgId: card.orgId,\n factoryProjectId: card.factoryProjectId,\n workItemId: card.id,\n supersededAt: new Date(),\n });\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n cleanupFailures.add(card.id);\n }\n }\n await rules.ingest(reconciledClosedEvent(repository, pullRequestNumber, state));\n await retireReconciledSubscriptions(options.integrationStorage, repository, pullRequestNumber, state.merged);\n for (const card of cards) {\n if (cleanupFailures.has(card.id)) continue;\n const trustStale = authorTrusted !== undefined && (card.metadata ?? {}).authorTrusted !== authorTrusted;\n try {\n await options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: {\n metadata: reconciledPullRequestMetadata(state, 'settled', trustStale ? authorTrusted : undefined),\n },\n });\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n if (state.merged) summary.merged += 1;\n else summary.closed += 1;\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n }\n return summary;\n };\n}\n\nexport function githubRulesOptions(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n): GithubRulesOptions | undefined {\n if (!context.runtime) return undefined;\n return {\n github,\n sourceControl: context.storage.sourceControl,\n integrationStorage: context.storage.generic,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n configVersion: context.runtime.configVersion,\n boards: context.runtime.boards,\n intake: context.storage.intake,\n };\n}\n\nexport function attachGithubRules(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n): ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined {\n const options = githubRulesOptions(github, context);\n if (!options) return undefined;\n const rules = new GithubRules(options);\n return event => rules.ingest(event);\n}\n\nexport function attachGithubReconciler(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n fetchPullRequest: GithubPullRequestFetcher,\n): GithubPullRequestReconciler | undefined {\n const options = githubRulesOptions(github, context);\n if (!options) return undefined;\n return createGithubPullRequestReconciler(options, fetchPullRequest);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rules.js","names":["#intakeTarget","#relocateLabeledIssue","#relatedItem","#handOffPush","#ingestProject","#factoryMentionTarget","#isFactoryLogin","#linkedClosureItem","#resolveItem"],"sources":["../../../src/integrations/github/rules.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport {\n boardEventHandlers,\n boardForWorkItem,\n eventBoardId,\n rulePhaseField,\n withBoardClosure,\n workItemPhaseSemantics,\n} from '../../boards/index.js';\nimport type { BoardRegistry } from '../../boards/index.js';\nimport { cardLabels, moveCardToBoard } from '../../boards/relocate.js';\nimport { selectEventRule } from '../../rules/event-rules.js';\nimport type {\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n} from '../../rules/types.js';\nimport { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from '../../rules/validation.js';\nimport { resolveIntakeLabelRoute } from '../../storage/domains/intake/base.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/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 type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport {\n FACTORY_PULL_REQUEST_RECONCILIATION_KEY,\n WorkItemUpdateConflictError,\n} from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext, IntegrationHooks } from '../base.js';\nimport { recordPullRequestHead } from '../pull-request-head.js';\nimport type { GithubAppIdentity } from './app-identity.js';\nimport type { GithubEventRules } from './default-rules.js';\nimport type { GithubRepositoryPermission } from './integration.js';\nimport { changeRequestTargetKey } from './subscriptions.js';\nimport type { ParsedGithubWebhook } from './webhook.js';\n\nconst TRUSTED_PERMISSIONS = new Set(['write', 'admin']);\nconst RULE_TIMEOUT_MS = 5_000;\nconst FACTORY_TRIAGE_COMMENT_MARKER = '<!-- mastra-factory-triage -->';\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\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 timestamp(value: unknown): number | undefined {\n const parsed = typeof value === 'string' ? Date.parse(value) : NaN;\n return Number.isNaN(parsed) ? undefined : parsed;\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\nfunction boolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction actorLogins(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(actor => {\n const login = string(object(actor)?.login);\n return login ? [login] : [];\n });\n}\n\nfunction sameLabels(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((label, index) => label === b[index]);\n}\n\nfunction labelNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(label => {\n if (typeof label === 'string') return label ? [label] : [];\n const name = string(object(label)?.name);\n return name ? [name] : [];\n });\n}\n\nfunction parseFactoryReviewCommand(\n body: string | undefined,\n target: string | undefined,\n): { command: 'review' | 're-review'; target: string } | undefined {\n if (!body || !target) return undefined;\n const firstLine = body\n .split('\\n')\n .map(line => line.trim())\n .find(line => line.length > 0)\n ?.toLowerCase();\n if (!firstLine) return undefined;\n const mention = `@${target.toLowerCase().replace(/\\[bot\\]$/, '')}`;\n if (firstLine === `${mention} review`) return { command: 'review', target };\n if (firstLine === `${mention} re-review`) return { command: 're-review', target };\n return undefined;\n}\n\nfunction eventName(parsed: ParsedGithubWebhook): FactoryGithubEventName | undefined {\n const action = string(parsed.payload.action);\n if (parsed.event === 'issues' && action === 'opened') return 'issueOpened';\n if (parsed.event === 'issues' && action === 'edited') {\n const changes = object(parsed.payload.changes);\n return object(changes?.title) || object(changes?.body) ? 'issueEdited' : undefined;\n }\n if (parsed.event === 'issues' && action === 'closed') return 'issueClosed';\n if (parsed.event === 'issue_comment') {\n const issue = object(parsed.payload.issue);\n // A comment on a PR arrives as `issue_comment` with `issue.pull_request` set.\n // It routes to the PR's own event so it binds to the authoring Work item via\n // provenance instead of being mistaken for a comment on an issue of the same\n // number. Only `created` matters: edits and deletions of an existing comment\n // are not new feedback to act on.\n if (object(issue?.pull_request)) {\n return action === 'created' ? 'pullRequestCommentCreated' : undefined;\n }\n if (action === 'created') return 'issueCommentCreated';\n if (action === 'edited') return 'issueCommentEdited';\n if (action === 'deleted') return 'issueCommentDeleted';\n }\n if (parsed.event === 'pull_request' && action === 'opened') return 'pullRequestOpened';\n if (parsed.event === 'pull_request' && action === 'synchronize') return 'pullRequestUpdated';\n if (parsed.event === 'pull_request' && action === 'closed') {\n return boolean(object(parsed.payload.pull_request)?.merged) ? 'pullRequestMerged' : 'pullRequestClosed';\n }\n if (parsed.event === 'pull_request' && action === 'review_requested') return 'pullRequestReviewRequested';\n if (parsed.event === 'pull_request_review' && action === 'submitted') return 'pullRequestReviewSubmitted';\n return undefined;\n}\n\n/**\n * Canonical source keys (`github-issue:N`, `github-pr:N`) do not identify a\n * repository, so a project linked to several repositories could bind repo A's\n * event to repo B's same-numbered card. The card's intake-stamped URL is\n * authoritative; the intake-stamped `githubRepositoryId` covers URL-less\n * cards. A card with neither signal cannot be attributed by number alone.\n */\nconst MAX_STALE_RULE_ATTEMPTS = 3;\n\nexport function cardBelongsToRepository(item: WorkItemRow, repositoryId: number, repositoryFullName: string): boolean {\n const url = item.externalSource?.url;\n if (url) {\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/(?:issues|pull)\\/\\d+(?:[/?#]|$)/.exec(url);\n if (match && match[1] === repositoryFullName) return true;\n }\n // A renamed repository leaves the old owner/name in the card URL, so a URL\n // mismatch still defers to the stable intake-stamped repository id.\n return item.metadata?.githubRepositoryId === repositoryId;\n}\n\nfunction canonicalSourceKey(kind: 'issue' | 'pull-request', itemNumber: number): string {\n return kind === 'issue' ? `github-issue:${itemNumber}` : `github-pr:${itemNumber}`;\n}\n\nfunction legacySourceKey(repositoryId: number, kind: 'issue' | 'pull-request', itemNumber: number): string {\n return `github:${repositoryId}:${kind}:${itemNumber}`;\n}\n\nfunction provenanceTarget(repositoryId: number, pullRequestNumber: number): string {\n return `factory-pr-provenance:${repositoryId}:${pullRequestNumber}`;\n}\n\nfunction workItemSource(item: WorkItemRow) {\n if (!item.externalSource) return 'manual' as const;\n return item.externalSource.type === 'pull-request' ? ('github-pr' as const) : ('github-issue' as const);\n}\n\nfunction workItemSourceKey(item: WorkItemRow): string | null {\n return item.externalSource?.externalId ?? null;\n}\n\n// Throws on a failed lookup: callers writing permanent state must retry, not\n// record the failure as distrust.\nexport async function trustedCollaborator(\n github: GithubRulesIntegration,\n input: { installationId: number; repository: string; login: string },\n): Promise<boolean> {\n const permission = await github.getRepositoryCollaboratorPermission(\n input.installationId,\n input.repository,\n input.login,\n );\n return permission !== undefined && TRUSTED_PERMISSIONS.has(permission);\n}\n\n// Terminal cards leave the reconcile loop below, so the ones that got there before author\n// trust was recorded have no other path to an answer.\nfunction authorAwaitingTrust(item: WorkItemRow, repository: ReconcileRepository): string | undefined {\n const metadata = item.metadata ?? {};\n if (typeof metadata.author !== 'string' || metadata.authorTrusted !== undefined) return undefined;\n const tracked = reconcilablePullRequestNumber(item, repository) ?? reconcilableIssueNumber(item, repository);\n if (tracked === undefined) return undefined;\n // A canonical key with no URL names no repository, and the pull request matcher takes it anyway.\n // Asking GitHub about the wrong repository of a multi-repository project would record a wrong answer.\n const unattributed =\n !item.externalSource?.url && /^github-(?:pr|issue):\\d+$/.test(item.externalSource?.externalId ?? '');\n if (unattributed && metadata.githubRepositoryId !== repository.id) return undefined;\n return metadata.author;\n}\n\nexport function sweepTrustLookup(\n github: GithubRulesIntegration,\n repository: ReconcileRepository,\n): (login: string) => Promise<boolean> {\n const cache = new Map<string, boolean>();\n return async login => {\n const cached = cache.get(login);\n if (cached !== undefined) return cached;\n const trusted = await trustedCollaborator(github, {\n installationId: repository.installationId,\n repository: repository.fullName,\n login,\n });\n cache.set(login, trusted);\n return trusted;\n };\n}\n\nasync function githubActor(\n github: GithubRulesIntegration,\n input: { installationId: number; repository: string; login: string; factoryAuthored: boolean },\n): Promise<FactoryRuleActor> {\n // The actor bit is recomputed on every event, so a failed lookup can read\n // untrusted for this one delivery instead of failing the ingest.\n const trusted = await trustedCollaborator(github, input).catch(() => false);\n return { type: 'github', login: input.login, trusted, factoryAuthored: input.factoryAuthored };\n}\n\ninterface FactoryPullRequestProvenanceData {\n kind: 'factory-pr-provenance';\n workItemId: string;\n}\n\nfunction pullRequestProvenance(\n data: Record<string, unknown> | undefined,\n factoryProjectId: string,\n): FactoryPullRequestProvenanceData | null {\n if (!data || data.kind !== 'factory-pr-provenance' || typeof data.workItemId !== 'string') return null;\n // Provenance proves which Factory *project's* run authored the PR. A row\n // written by a sibling project in the same org — or a legacy row without the\n // project stamp — fails closed here: honoring it would brand the PR\n // Factory-authored in a project that never touched it, and auto-start a\n // review that checks out and executes the PR there.\n if (data.factoryProjectId !== factoryProjectId) return null;\n return { kind: 'factory-pr-provenance', workItemId: data.workItemId };\n}\n\nexport interface GithubRulesIntegration {\n readonly rules: GithubEventRules;\n readonly slug?: string;\n /**\n * Factory's own GitHub login, used to ignore its own writes. Optional because\n * not every integration can name itself; when absent, self-recognition falls\n * back to the configured slug and, failing that, to content Factory stamps\n * itself (see `FACTORY_TRIAGE_COMMENT_MARKER`).\n */\n readonly identity?: GithubAppIdentity;\n getRepositoryCollaboratorPermission(\n installationId: number,\n repoFullName: string,\n username: string,\n ): Promise<GithubRepositoryPermission | undefined>;\n}\n\nexport interface GithubRulesOptions {\n github: GithubRulesIntegration;\n sourceControl: SourceControlStorageHandle;\n /** Integration-scoped storage; provenance rows are validated at read. */\n integrationStorage: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n storage: WorkItemsStorage;\n configVersion: string;\n boards: BoardRegistry;\n /**\n * Decides which installed board an issue lands on: a label route first, then the repository's\n * intake source binding. Absent means Work.\n */\n intake?: Pick<IntakeStorage, 'listLabelRoutes' | 'listBindings'>;\n /** Hands a push to a linked repository off for an environment build. */\n onRepositoryPush?: IntegrationHooks['onRepositoryPush'];\n}\n\n/** Identity under which label-driven relocations are recorded. */\nconst LABEL_ROUTE_USER_ID = 'factory-rule-dispatcher';\n\nfunction issueLabelChange(parsed: ParsedGithubWebhook): boolean {\n const action = string(parsed.payload.action);\n return parsed.event === 'issues' && (action === 'labeled' || action === 'unlabeled');\n}\n\n/**\n * Whether a login is Factory itself. Prefers the resolved identity, which is\n * observed from Factory's own writes, and falls back to the configured slug.\n */\nexport function isFactoryGithubLogin(\n github: Pick<GithubRulesIntegration, 'identity' | 'slug'>,\n login: string | null | undefined,\n): boolean {\n if (github.identity?.known) return github.identity.matches(login);\n const slug = github.slug?.trim();\n if (!slug || !login) return false;\n return login.toLowerCase() === `${slug.toLowerCase()}[bot]`;\n}\n\nexport class GithubRules {\n constructor(private readonly options: GithubRulesOptions) {}\n\n /**\n * Whether a login is Factory itself. Prefers the resolved identity, which is\n * observed from Factory's own writes, and falls back to the configured slug.\n * An unset slug must not silently answer \"not Factory\" — that is what\n * disabled every self-loop guard.\n */\n #isFactoryLogin(login: string | undefined): boolean {\n return isFactoryGithubLogin(this.options.github, login);\n }\n\n #factoryMentionTarget(): string | undefined {\n const identity = this.options.github.identity;\n if (identity?.known) return identity.login;\n const slug = this.options.github.slug?.trim();\n return slug ? `${slug.toLowerCase()}[bot]` : undefined;\n }\n\n /**\n * Board an issue lands on: the one its labels select under the project's label routes, else the\n * board the repository's intake source binding names. Only installed boards count. Undefined\n * leaves built-in routing (Work) in charge.\n */\n async #intakeTarget(\n project: ExternalRepositoryProjectTarget,\n repositoryName: string,\n labels: readonly string[],\n ): Promise<{ board: string; initialPhase: string } | undefined> {\n const intake = this.options.intake;\n if (!intake) return undefined;\n const { orgId, factoryProjectId } = project;\n if (labels.length > 0) {\n const routes = await intake.listLabelRoutes({ orgId, factoryProjectId, integrationId: 'github' });\n const route = resolveIntakeLabelRoute(routes, labels);\n const board = route ? this.options.boards.get(route.board) : undefined;\n if (board) return { board: board.id, initialPhase: board.initialPhase };\n }\n // GitHub bindings are keyed by the internal repository id; the slug is accepted too since the\n // bindings API stores whatever source id it is given.\n const slug = repositoryName.toLowerCase();\n const binding = (await intake.listBindings({ orgId, integrationId: 'github' })).find(\n candidate =>\n candidate.factoryProjectId === factoryProjectId &&\n (candidate.sourceId === project.projectRepository.repositoryId || candidate.sourceId.toLowerCase() === slug),\n );\n const board = binding?.board ? this.options.boards.get(binding.board) : undefined;\n return board ? { board: board.id, initialPhase: board.initialPhase } : undefined;\n }\n\n /**\n * `labeled` / `unlabeled` are not rule events: the labels only decide which board the card belongs\n * on, so the card is re-routed here and its label metadata refreshed. Cards a run owns, and\n * finished cards, stay where they are.\n */\n async #relocateLabeledIssue(\n parsed: ParsedGithubWebhook,\n repositoryId: number,\n repositoryName: string,\n project: ExternalRepositoryProjectTarget,\n ): Promise<{ status: 'ignored' | 'committed' }> {\n const issue = object(parsed.payload.issue);\n const issueNumber = issue?.pull_request === undefined ? number(issue?.number) : undefined;\n if (!issueNumber) return { status: 'ignored' };\n const found = await this.#relatedItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n issueNumber,\n undefined,\n undefined,\n null,\n );\n if (!found || found.externalSource?.type !== 'issue') return { status: 'ignored' };\n const labels = labelNames(issue?.labels);\n let item = found;\n let changed = false;\n if (!sameLabels(cardLabels(item), labels)) {\n let updated;\n try {\n updated = await this.options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: LABEL_ROUTE_USER_ID,\n patch: { metadata: { ...(item.metadata ?? {}), labels } },\n expectedRevision: item.revision,\n });\n } catch (error) {\n // The card changed under us (a run wrote it, or a concurrent delivery won). The next\n // delivery or reconcile pass carries the same labels, so drop this one instead of 5xx-ing.\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n return { status: 'ignored' };\n }\n if (!updated) return { status: 'ignored' };\n item = updated.item;\n changed = true;\n }\n const target = await this.#intakeTarget(project, repositoryName, labels);\n const outcome = await moveCardToBoard({\n workItems: this.options.storage,\n boardRegistry: this.options.boards,\n userId: LABEL_ROUTE_USER_ID,\n item,\n targetBoard: target?.board ?? 'work',\n });\n return { status: changed || outcome === 'moved' ? 'committed' : 'ignored' };\n }\n\n async ingest(parsed: ParsedGithubWebhook): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> {\n const event = eventName(parsed);\n const labelChange = issueLabelChange(parsed);\n const repository = object(parsed.payload.repository);\n const installationId = number(object(parsed.payload.installation)?.id);\n const repositoryId = number(repository?.id);\n const repositoryName = string(repository?.full_name);\n const login = string(object(parsed.payload.sender)?.login);\n if (parsed.event === 'push' && installationId && repositoryId) {\n await this.#handOffPush(parsed, installationId, repositoryId);\n return { status: 'ignored' };\n }\n if ((!event && !labelChange) || !installationId || !repositoryId || !repositoryName || !login) {\n return { status: 'ignored' };\n }\n\n const projects = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: String(installationId),\n repositoryExternalId: String(repositoryId),\n });\n if (projects.length === 0) return { status: 'ignored' };\n const results = [];\n for (const project of projects) {\n results.push(\n event\n ? await this.#ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, project)\n : await this.#relocateLabeledIssue(parsed, repositoryId, repositoryName, project),\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 * A push is not a work-item event; it only reaches the environment build\n * hand-off, once per project the repository is linked to. The hook decides\n * whether the project builds on push.\n */\n async #handOffPush(parsed: ParsedGithubWebhook, installationId: number, repositoryId: number): Promise<void> {\n const onRepositoryPush = this.options.onRepositoryPush;\n const ref = string(parsed.payload.ref);\n const defaultBranch = string(object(parsed.payload.repository)?.default_branch);\n if (!onRepositoryPush || !ref || !defaultBranch) return;\n const projects = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: String(installationId),\n repositoryExternalId: String(repositoryId),\n });\n for (const project of projects) {\n onRepositoryPush({\n orgId: project.orgId,\n factoryProjectId: project.factoryProjectId,\n projectRepository: {\n id: project.projectRepository.id,\n inEnvironment: project.projectRepository.inEnvironment,\n },\n ref,\n defaultBranch,\n });\n }\n }\n\n async #ingestProject(\n parsed: ParsedGithubWebhook,\n event: FactoryGithubEventName,\n installationId: number,\n repositoryId: number,\n repositoryName: string,\n login: string,\n project: ExternalRepositoryProjectTarget,\n ): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> {\n const factoryProject = await this.options.projects.get({\n orgId: project.orgId,\n id: project.factoryProjectId,\n });\n if (!factoryProject) return { status: 'missing' };\n const issue = object(parsed.payload.issue);\n const issueComment = object(parsed.payload.comment);\n const changes = object(parsed.payload.changes);\n // A comment on a PR carries the PR under `issue` (with `pull_request` set)\n // and has no `pull_request` payload of its own. Read the PR from `issue` in\n // that case so provenance and `context.pullRequest` behave as they do for\n // every other PR event, and so the number is never treated as an issue's.\n const commentOnPullRequest = object(parsed.payload.issue)?.pull_request !== undefined;\n const pullRequest = object(parsed.payload.pull_request) ?? (commentOnPullRequest ? issue : undefined);\n const issueNumber = commentOnPullRequest ? undefined : number(issue?.number);\n const pullRequestNumber = number(pullRequest?.number);\n const provenance = pullRequestNumber\n ? pullRequestProvenance(\n (\n await this.options.integrationStorage.subscriptions.listByTarget(\n provenanceTarget(repositoryId, pullRequestNumber),\n { status: 'active' },\n )\n ).find(\n subscription =>\n subscription.orgId === project.orgId && subscription.data?.factoryProjectId === project.factoryProjectId,\n )?.data,\n project.factoryProjectId,\n )\n : null;\n // Re-review events target the PR's own Review card, not the Work item that\n // provenance would otherwise bind the event to. For review_requested the\n // sender is whoever clicked re-request, so a Factory-authored PR must not\n // brand a human requester as factory-authored.\n const reviewRequested = event === 'pullRequestReviewRequested';\n const reviewCommand =\n event === 'pullRequestCommentCreated'\n ? parseFactoryReviewCommand(string(issueComment?.body), this.#factoryMentionTarget())\n : undefined;\n const reviewEntryRequested = reviewRequested || reviewCommand !== undefined;\n // Provenance proves the *pull request* came from Factory, which is not the\n // same as the sender of this event. For events where the sender is whoever\n // reacted to the PR — re-requesting review, commenting, submitting a review\n // — branding them from provenance would mark every human and every review\n // bot as Factory. Only the app login identifies Factory for those.\n const senderIsResponder =\n reviewRequested || event === 'pullRequestCommentCreated' || event === 'pullRequestReviewSubmitted';\n const reReviewEvent = reviewEntryRequested || event === 'pullRequestUpdated';\n const requestedReviewer = string(object(parsed.payload.requested_reviewer)?.login);\n const pullRequestAuthor = string(object(pullRequest?.user)?.login);\n const pullRequestFactoryAuthored = provenance !== null || this.#isFactoryLogin(pullRequestAuthor);\n const resolvedItem = await this.#relatedItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n issueNumber,\n pullRequestNumber,\n string(object(pullRequest?.head)?.ref),\n reReviewEvent ? null : provenance,\n senderIsResponder && !reviewEntryRequested,\n );\n // A review-entry request must never treat a branch-matched authoring Work\n // card as the PR's Review card. A missing Review card is materialized below.\n const relatedItem =\n reviewEntryRequested && resolvedItem?.externalSource?.type !== 'pull-request' ? undefined : resolvedItem;\n const intake = issueNumber\n ? await this.#intakeTarget(project, repositoryName, labelNames(issue?.labels))\n : undefined;\n const actor = await githubActor(this.options.github, {\n installationId,\n repository: repositoryName,\n login,\n factoryAuthored: (!senderIsResponder && provenance !== null) || this.#isFactoryLogin(login),\n });\n // A marked handoff comment is ignored only when Factory authored it: a\n // human may quote the marker to add an investigation lead, and that must\n // still retrigger triage. Recognising the author is therefore the whole\n // guard — when identity cannot be resolved this fails open and Factory's\n // own handoff cancels the run that wrote it.\n if (\n actor.type === 'github' &&\n actor.factoryAuthored &&\n (event === 'issueCommentCreated' || event === 'issueCommentEdited') &&\n string(issueComment?.body)?.includes(FACTORY_TRIAGE_COMMENT_MARKER)\n ) {\n return { status: 'ignored' };\n }\n // One delivery can concern two cards — a merged pull request settles both\n // its own Review card and the Work item that authored it — and every\n // decision a rule returns is committed against a single item, at that\n // item's revision. So the evaluation, not the decision, is what fans out:\n // the rule runs once per bound item, each with its own ingress identity.\n const evaluate = async (\n item: WorkItemRow | undefined,\n ingressIdentity: string,\n /** Set on the evaluation that files the pull request's own Review card. */\n pullRequestIntake = false,\n attempt = 1,\n ): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> => {\n const context: FactoryGithubRuleContext = {\n tenant: { orgId: project.orgId, projectId: project.factoryProjectId },\n actor,\n ingress: { type: 'github', id: ingressIdentity },\n cause: `github.${event}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n ...(item\n ? {\n item: {\n id: item.id,\n source: workItemSource(item),\n sourceKey: workItemSourceKey(item),\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: item.stages,\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board: boardForWorkItem(item),\n itemRevision: item.revision,\n ...withBoardClosure(this.options.boards, item),\n ...rulePhaseField(this.options.boards, item),\n }\n : {}),\n ...(intake ? { intake } : {}),\n ...(pullRequestIntake ? { pullRequestIntake: true } : {}),\n event,\n deliveryId: parsed.deliveryId,\n factory: { createdAt: factoryProject.createdAt.toISOString() },\n repository: { id: repositoryId, fullName: repositoryName, installationId },\n ...(issueNumber && string(issue?.title) && string(issue?.html_url)\n ? {\n issue: {\n number: issueNumber,\n title: string(issue?.title)!,\n url: string(issue?.html_url)!,\n ...(string(issue?.created_at) ? { createdAt: string(issue?.created_at) } : {}),\n ...(string(issue?.updated_at) ? { updatedAt: string(issue?.updated_at) } : {}),\n assignees: actorLogins(issue?.assignees),\n labels: labelNames(issue?.labels),\n ...(string(issue?.state) === 'closed' || string(issue?.state) === 'open'\n ? { state: string(issue?.state) as 'open' | 'closed' }\n : {}),\n ...(string(issue?.state_reason) ? { stateReason: string(issue?.state_reason) } : {}),\n },\n }\n : {}),\n ...(event === 'issueEdited'\n ? { issueChange: { title: Boolean(object(changes?.title)), body: Boolean(object(changes?.body)) } }\n : {}),\n ...(number(issueComment?.id)\n ? {\n issueComment: {\n id: number(issueComment?.id)!,\n ...(string(issueComment?.body) ? { body: string(issueComment?.body) } : {}),\n ...(string(issueComment?.html_url) ? { url: string(issueComment?.html_url) } : {}),\n ...(string(object(issueComment?.user)?.login)\n ? { author: string(object(issueComment?.user)?.login) }\n : {}),\n ...(string(object(issueComment?.user)?.type)\n ? { authorType: string(object(issueComment?.user)?.type) }\n : {}),\n ...(string(issueComment?.created_at) ? { createdAt: string(issueComment?.created_at) } : {}),\n ...(string(issueComment?.updated_at) ? { updatedAt: string(issueComment?.updated_at) } : {}),\n },\n }\n : {}),\n ...(pullRequestNumber && string(pullRequest?.title) && string(pullRequest?.html_url)\n ? {\n pullRequest: {\n number: pullRequestNumber,\n title: string(pullRequest?.title)!,\n url: string(pullRequest?.html_url)!,\n ...(string(pullRequest?.created_at) ? { createdAt: string(pullRequest?.created_at) } : {}),\n state: string(pullRequest?.state) === 'closed' ? ('closed' as const) : ('open' as const),\n draft: boolean(pullRequest?.draft) ?? false,\n merged: boolean(pullRequest?.merged) ?? false,\n assignees: actorLogins(pullRequest?.assignees),\n requestedReviewers: actorLogins(pullRequest?.requested_reviewers),\n labels: labelNames(pullRequest?.labels),\n ...(pullRequestAuthor ? { author: pullRequestAuthor } : {}),\n factoryAuthored: pullRequestFactoryAuthored,\n headBranch: string(object(pullRequest?.head)?.ref) ?? '',\n baseBranch: string(object(pullRequest?.base)?.ref) ?? '',\n },\n }\n : {}),\n ...(reviewRequested && requestedReviewer\n ? {\n reviewRequest: {\n reviewer: requestedReviewer,\n factoryReviewer: this.#isFactoryLogin(requestedReviewer),\n },\n }\n : {}),\n ...(reviewCommand ? { reviewCommand } : {}),\n ...(object(parsed.payload.review)\n ? {\n review: {\n id: number(object(parsed.payload.review)?.id) ?? 0,\n state: string(object(parsed.payload.review)?.state) ?? 'unknown',\n url: string(object(parsed.payload.review)?.html_url) ?? '',\n ...(string(object(object(parsed.payload.review)?.user)?.login)\n ? { author: string(object(object(parsed.payload.review)?.user)?.login) }\n : {}),\n ...(string(object(parsed.payload.review)?.body)\n ? { body: string(object(parsed.payload.review)?.body) }\n : {}),\n },\n }\n : {}),\n };\n\n const rule = selectEventRule(\n this.options.github.rules,\n boardEventHandlers(this.options.boards, eventBoardId(context), 'github'),\n event,\n );\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(entry, this.options.boards, item ? boardForWorkItem(item) : undefined);\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 GitHub rule failed.',\n };\n }\n\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: project.orgId,\n factoryProjectId: project.factoryProjectId,\n workItemId: item?.id ?? null,\n ingress: { identity: ingressIdentity, triggerType: `github.${event}` },\n configVersion: this.options.configVersion,\n expectedRevision: item?.revision ?? null,\n actor: { ...actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n if (committed.status === 'stale') {\n // The item was written concurrently. Re-read it and run the rule again\n // against the fresh revision; nothing was persisted for this attempt.\n if (attempt >= MAX_STALE_RULE_ATTEMPTS) {\n throw new Error(`Factory GitHub rule evaluation for ${ingressIdentity} kept losing revision races.`);\n }\n const fresh = item ? await this.options.storage.get({ orgId: project.orgId, id: item.id }) : null;\n if (item && !fresh) return { status: 'missing' };\n return evaluate(fresh ?? undefined, ingressIdentity, pullRequestIntake, attempt + 1);\n }\n return { status: committed.status };\n };\n\n const deliveryIdentity = `${installationId}:${parsed.deliveryId}`;\n // A pull request opening concerns two cards: its own Review card, which the\n // arrival rule files — committed against the Work item that authored the\n // pull request when one exists, because that binding is what links the two\n // — and that authoring item, which is now out for review. The arrival is\n // the delivery's own evaluation; the item's is a second one, under an\n // identity suffixed with its id, because ingress identities are the replay\n // key and reusing the delivery's own would drop it as a duplicate.\n const pullRequestOpened = event === 'pullRequestOpened';\n const primary = await evaluate(relatedItem, deliveryIdentity, pullRequestOpened);\n // A closed pull request is the one *other* event both linked cards need:\n // the Review card has to close, and the Work item that wrote the code has\n // to finish (on merge) or at least stop recording it as open. Resolution binds the delivery to\n // whichever card it matched first, so evaluate the other one too.\n const linked =\n (event === 'pullRequestMerged' || event === 'pullRequestClosed') && relatedItem && pullRequestNumber\n ? await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n relatedItem,\n )\n : undefined;\n // Moving the card to Done does not touch its pull request fields, so without\n // this the card reads \"open\" until the next sweep and still offers Re-review.\n // Stamped after the evaluations, which commit at the revision they read.\n const stampClosed = async (companionStatus?: string) => {\n if (!pullRequestNumber) return;\n // The authoring Work item records which pull request it has out, so an\n // agent cannot close the work while that pull request is still open.\n if (pullRequestOpened) {\n // Mirrors the out-for-review rule's trust gate. A replayed opened\n // delivery only restores the mark (after a failed write) while the\n // pull request's Review card still records it open, so a redelivery\n // after the close cannot block the Work item again.\n const trusted = (actor.type === 'github' && actor.trusted) || pullRequestFactoryAuthored;\n const reviewCard =\n authoringItem && trusted\n ? await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n authoringItem,\n )\n : undefined;\n const stillOpen =\n companionStatus === 'replayed'\n ? reviewCard !== undefined && reviewCard.metadata?.state !== 'closed'\n : reviewCard?.metadata?.state !== 'closed';\n if (authoringItem && trusted && stillOpen) {\n // The head comes along so a head recorded for an earlier pull\n // request cannot mark this one's verdict outdated. A replayed opening\n // of the pull request already recorded keeps the head its pushes left.\n const rebinding = authoringItem.metadata?.openPullRequestNumber !== pullRequestNumber;\n const head = rebinding\n ? {\n pullRequestHeadSha: string(object(pullRequest?.head)?.sha)?.toLowerCase() ?? null,\n pullRequestHeadAt: timestamp(pullRequest?.updated_at) ?? null,\n }\n : {};\n await this.options.storage.update({\n orgId: authoringItem.orgId,\n id: authoringItem.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { openPullRequestNumber: pullRequestNumber, ...head } },\n });\n }\n return;\n }\n if (event !== 'pullRequestMerged' && event !== 'pullRequestClosed') return;\n for (const card of [relatedItem, linked]) {\n if (!card) continue;\n if (card.externalSource?.type !== 'pull-request') {\n // A merge settles the review, so the authoring Work item's mirrored\n // \"request changes\" no longer stops it from being closed.\n // Only the pull request the card has out settles it; an older one\n // merging must not clear a verdict that belongs to its successor.\n const recorded = card.metadata?.openPullRequestNumber;\n const ownsCard = typeof recorded !== 'number' || recorded === pullRequestNumber;\n const settled = {\n ...(ownsCard && event === 'pullRequestMerged' && typeof card.metadata?.reviewVerdict === 'string'\n ? { reviewVerdict: null }\n : {}),\n ...(recorded === pullRequestNumber ? { openPullRequestNumber: null } : {}),\n };\n if (Object.keys(settled).length > 0) {\n await this.options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: settled },\n });\n }\n continue;\n }\n await this.options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { state: 'closed', merged: event === 'pullRequestMerged' } },\n });\n }\n };\n const authoringItem =\n linked === undefined &&\n pullRequestOpened &&\n relatedItem !== undefined &&\n relatedItem.externalSource?.type !== 'pull-request'\n ? relatedItem\n : undefined;\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 stampHead = async () => {\n const headSha = string(object(pullRequest?.head)?.sha)?.toLowerCase();\n if (event !== 'pullRequestUpdated' || !headSha || !pullRequestNumber) return;\n const headAt = timestamp(pullRequest?.updated_at);\n if (relatedItem?.externalSource?.type !== 'pull-request') return;\n const parent = await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n relatedItem,\n );\n // The authoring Work item mirrors the verdict of the pull 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: relatedItem.orgId,\n id: relatedItem.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: pullRequestNumber,\n });\n }\n };\n if (linked === undefined && authoringItem === undefined) {\n await stampClosed();\n await stampHead();\n return primary;\n }\n const companion = linked ?? authoringItem;\n const secondary = await evaluate(companion, `${deliveryIdentity}:${companion?.id ?? 'pull-request'}`);\n await stampClosed(secondary.status);\n for (const status of ['committed', 'replayed'] as const) {\n if (primary.status === status || secondary.status === status) return { status };\n }\n return primary;\n }\n\n /**\n * The other card a closed pull request concerns, joined through the Review\n * card's `parentWorkItemId` — the link `upsertLinkedWorkItem` records when the\n * pull request is opened, and therefore an exact join that needs no branch\n * heuristics. Returns nothing when the pull request has only one card, which\n * is every pull request Factory did not open from a work item's session.\n */\n async #linkedClosureItem(\n orgId: string,\n projectId: string,\n repositoryId: number,\n repositoryFullName: string,\n pullRequestNumber: number,\n resolved: WorkItemRow,\n ): Promise<WorkItemRow | undefined> {\n const items = await this.options.storage.list({ orgId, factoryProjectId: projectId });\n const linked =\n resolved.externalSource?.type === 'pull-request'\n ? // Bound to the pull request's own Review card: follow the recorded\n // link back to the work item that authored it.\n items.find(item => item.id === resolved.parentWorkItemId)\n : // Bound to the work item (provenance): find the Review card this pull\n // request opened, and only when it names this item as its parent — a\n // card for the same number in another repository is not this one's.\n items.find(\n item =>\n item.parentWorkItemId === resolved.id &&\n (item.externalSource?.externalId === canonicalSourceKey('pull-request', pullRequestNumber) ||\n item.externalSource?.externalId === legacySourceKey(repositoryId, 'pull-request', pullRequestNumber)) &&\n cardBelongsToRepository(item, repositoryId, repositoryFullName),\n );\n return linked?.id === resolved.id ? undefined : linked;\n }\n\n async #relatedItem(\n orgId: string,\n projectId: string,\n repositoryId: number,\n repositoryFullName: string,\n issueNumber: number | undefined,\n pullRequestNumber: number | undefined,\n pullRequestHeadBranch: string | undefined,\n provenance: FactoryPullRequestProvenanceData | null,\n preferAuthoringItem = false,\n ): Promise<WorkItemRow | undefined> {\n const items = await this.options.storage.list({ orgId, factoryProjectId: projectId });\n const resolved = this.#resolveItem(\n items,\n repositoryId,\n repositoryFullName,\n issueNumber,\n pullRequestNumber,\n pullRequestHeadBranch,\n provenance,\n );\n // Feedback on a pull request has to reach the item that *wrote* the code.\n // Provenance normally lands it there directly, but when provenance is\n // missing the PR-number lookup wins and returns the PR's own Review card\n // instead — a board the feedback rules deliberately refuse to act on, so\n // the wake is silently dropped. The linked card records its author in\n // `parentWorkItemId`, so follow that link back rather than relaxing the\n // guard, which would let a Review card react to its own posted review.\n if (preferAuthoringItem && resolved?.externalSource?.type === 'pull-request' && resolved.parentWorkItemId) {\n return items.find(item => item.id === resolved.parentWorkItemId) ?? resolved;\n }\n return resolved;\n }\n\n #resolveItem(\n items: WorkItemRow[],\n repositoryId: number,\n repositoryFullName: string,\n issueNumber: number | undefined,\n pullRequestNumber: number | undefined,\n pullRequestHeadBranch: string | undefined,\n provenance: FactoryPullRequestProvenanceData | null,\n ): WorkItemRow | undefined {\n if (provenance) return items.find(item => item.id === provenance.workItemId);\n if (issueNumber) {\n return (\n items.find(\n item =>\n item.externalSource?.externalId === canonicalSourceKey('issue', issueNumber) &&\n cardBelongsToRepository(item, repositoryId, repositoryFullName),\n ) ?? items.find(item => item.externalSource?.externalId === legacySourceKey(repositoryId, 'issue', issueNumber))\n );\n }\n if (pullRequestNumber) {\n return (\n items.find(\n item =>\n item.externalSource?.externalId === canonicalSourceKey('pull-request', pullRequestNumber) &&\n cardBelongsToRepository(item, repositoryId, repositoryFullName),\n ) ??\n items.find(\n item => item.externalSource?.externalId === legacySourceKey(repositoryId, 'pull-request', pullRequestNumber),\n ) ??\n // Provenance fallback: a PR pushed from a work item's session branch\n // belongs to that item even when no gh-pr-create provenance was\n // recorded (session predating state seeding, or the PR was opened\n // outside the tracked tool call). Session branches are per-item\n // (`factory/issue-N`), so a head-branch match is unambiguous.\n (pullRequestHeadBranch\n ? items.find(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === pullRequestHeadBranch),\n )\n : undefined)\n );\n }\n return undefined;\n }\n}\n\nexport interface ReconcilePullRequestState {\n title: string;\n url: string;\n state: 'open' | 'closed';\n draft: boolean;\n merged: boolean;\n assignees?: string[];\n requestedReviewers?: string[];\n labels?: string[];\n headBranch: string;\n baseBranch: string;\n author?: string;\n createdAt?: string;\n mergedBy?: string;\n}\n\nexport interface PolledPullRequestState extends ReconcilePullRequestState {\n createdAt: string;\n}\n\nexport type GithubPullRequestFetcher = (input: {\n installationId: number;\n repository: string;\n number: number;\n}) => Promise<ReconcilePullRequestState | undefined>;\n\nexport interface ReconcileIssueState {\n title: string;\n url: string;\n state: 'open' | 'closed';\n /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */\n stateReason?: string;\n assignees?: string[];\n labels?: string[];\n author?: string;\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport type GithubIssueFetcher = (input: {\n installationId: number;\n repository: string;\n number: number;\n}) => Promise<ReconcileIssueState | undefined>;\n\n/** Lists a repository's open issues (pull requests excluded) for missed-open discovery. */\nexport type GithubOpenIssueLister = (input: {\n installationId: number;\n repository: string;\n}) => Promise<Array<ReconcileIssueState & { number: number }>>;\n\nexport interface ReconcileRepository {\n id: number;\n fullName: string;\n installationId: number;\n}\n\nexport interface ReconcileSweepSummary {\n /** Factory-configured repositories included in the sweep. */\n repositories: number;\n /** PRs whose live state was fetched from GitHub. */\n checked: number;\n /** Missed merges replayed through the rules ingress. */\n merged: number;\n /** Missed closes-without-merge replayed through the rules ingress. */\n closed: number;\n /** PRs/issues (or whole repositories) skipped because of an error. */\n failed: number;\n /** Error samples with context, capped at {@link RECONCILE_ERROR_SAMPLE_LIMIT}. */\n errors: Array<{ repository: string; pullRequestNumber?: number; issueNumber?: number; error: string }>;\n}\n\nexport type GithubPullRequestReconciler = (repositories: ReconcileRepository[]) => Promise<ReconcileSweepSummary>;\n\nexport const RECONCILE_ERROR_SAMPLE_LIMIT = 5;\n\nexport function sameStrings(left: unknown, right: string[] | undefined): boolean {\n if (right === undefined) return true;\n if (!Array.isArray(left)) return false;\n const leftValues = new Set(left.flatMap(value => (typeof value === 'string' ? [value] : [])));\n const rightValues = new Set(right);\n return leftValues.size === rightValues.size && [...leftValues].every(value => rightValues.has(value));\n}\n\n/**\n * Extracts the PR number a work item tracks, but only when the item belongs\n * to the given repository. Card URLs pin the repository unambiguously; the\n * legacy source key embeds the repository id. Canonical keys (`github-pr:N`)\n * carry no repository, so they are only trusted when the item has no URL —\n * a project mapped to multiple repositories must not reconcile one repo's\n * card against another repo's PR number.\n */\nfunction reconcilablePullRequestNumber(item: WorkItemRow, repository: ReconcileRepository): number | undefined {\n if (item.externalSource?.type !== 'pull-request') return undefined;\n const url = item.externalSource.url;\n if (url) {\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/pull\\/(\\d+)(?:[/?#]|$)/.exec(url);\n if (!match) return undefined;\n return match[1] === repository.fullName ? Number(match[2]) : undefined;\n }\n const externalId = item.externalSource.externalId;\n const legacy = /^github:(\\d+):pull-request:(\\d+)$/.exec(externalId);\n if (legacy) return Number(legacy[1]) === repository.id ? Number(legacy[2]) : undefined;\n const canonical = /^github-pr:(\\d+)$/.exec(externalId);\n return canonical ? Number(canonical[1]) : undefined;\n}\n\n/**\n * Extracts the issue number a work item tracks, but only when the item\n * belongs to the given repository. Stricter than\n * {@link reconcilablePullRequestNumber}: a canonical key with no URL is only\n * trusted when the intake-stamped `githubRepositoryId` confirms the\n * repository, because the sweep initiates closes on its own.\n */\nexport function reconcilableIssueNumber(item: WorkItemRow, repository: ReconcileRepository): number | undefined {\n if (item.externalSource?.type !== 'issue') return undefined;\n const url = item.externalSource.url;\n if (url) {\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/issues\\/(\\d+)(?:[/?#]|$)/.exec(url);\n if (!match) return undefined;\n // A renamed repository leaves the old owner/name in the card URL, so a\n // URL mismatch still defers to the stable intake-stamped repository id.\n const belongs = match[1] === repository.fullName || item.metadata?.githubRepositoryId === repository.id;\n return belongs ? Number(match[2]) : undefined;\n }\n const externalId = item.externalSource.externalId;\n const legacy = /^github:(\\d+):issue:(\\d+)$/.exec(externalId);\n if (legacy) return Number(legacy[1]) === repository.id ? Number(legacy[2]) : undefined;\n const canonical = /^github-issue:(\\d+)$/.exec(externalId);\n if (!canonical) return undefined;\n // Canonical keys carry no repository; only the intake-stamped repository id\n // can attribute a URL-less card, and guessing would let a multi-repo\n // project close repo B's card because repo A's same-numbered issue closed.\n return item.metadata?.githubRepositoryId === repository.id ? Number(canonical[1]) : undefined;\n}\n\nexport function reconciledIssueClosedEvent(\n repository: ReconcileRepository,\n issueNumber: number,\n state: ReconcileIssueState,\n): ParsedGithubWebhook {\n return {\n event: 'issues',\n // Stable per (repository, issue): the ingress dedupe makes repeat\n // reconcile cycles replay instead of re-committing decisions.\n deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:closed`,\n payload: {\n action: 'closed',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.author ?? 'github' },\n issue: {\n number: issueNumber,\n title: state.title,\n html_url: state.url,\n state: 'closed',\n ...(state.stateReason ? { state_reason: state.stateReason } : {}),\n ...(state.createdAt ? { created_at: state.createdAt } : {}),\n ...(state.updatedAt ? { updated_at: state.updatedAt } : {}),\n assignees: githubUsers(state.assignees),\n },\n },\n };\n}\n\n/**\n * The label-drift replay: an open issue whose labels changed without a\n * `labeled`/`unlabeled` webhook ever arriving (Factory was down, the delivery\n * was lost, or the label was applied by something other than a delivery the\n * webhook saw). Synthesized as an `opened` delivery so the deployment's\n * `issueOpened` rule decides placement, exactly as it does at arrival — one\n * source of placement policy for both.\n */\nexport function reconciledIssueRelabeledEvent(\n repository: ReconcileRepository,\n issueNumber: number,\n state: ReconcileIssueState,\n): ParsedGithubWebhook {\n const labels = state.labels ?? [];\n // GitHub updates `updated_at` for a label change. Including it makes retries\n // of one observed issue version idempotent while allowing A → B → A to replay\n // its final A placement as a new version. Synthetic callers without it retain\n // the legacy label-only identity.\n const digest = createHash('sha256')\n .update([...labels].sort().join('\\n'))\n .digest('hex')\n .slice(0, 16);\n const version = state.updatedAt ? `:${createHash('sha256').update(state.updatedAt).digest('hex').slice(0, 16)}` : '';\n return {\n event: 'issues',\n deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:relabeled:${digest}${version}`,\n payload: {\n // `opened` is the event that carries an issue's labels through the rules;\n // an already-filed card is re-placed by the decision, not re-materialized.\n action: 'opened',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.author ?? 'github' },\n issue: {\n number: issueNumber,\n title: state.title,\n html_url: state.url,\n state: 'open',\n ...(state.createdAt ? { created_at: state.createdAt } : {}),\n ...(state.updatedAt ? { updated_at: state.updatedAt } : {}),\n assignees: githubUsers(state.assignees),\n labels: githubLabels(labels),\n },\n },\n };\n}\n\n/**\n * The missed-open replay: an open issue with no Work card, because its\n * `opened` delivery never reached the rules ingress. Synthesized as the same\n * `opened` delivery so the deployment's `issueOpened` rule decides whether and\n * where it lands, exactly as it would have at arrival. The delivery id is\n * stable per issue so repeat sweeps dedupe at the ingress.\n */\nexport function reconciledIssueOpenedEvent(\n repository: ReconcileRepository,\n issueNumber: number,\n state: ReconcileIssueState,\n): ParsedGithubWebhook {\n const event = reconciledIssueRelabeledEvent(repository, issueNumber, state);\n return { ...event, deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:opened` };\n}\n\nexport function polledPullRequestEvent(\n repository: ReconcileRepository,\n pullRequestNumber: number,\n state: PolledPullRequestState,\n): ParsedGithubWebhook {\n return {\n event: 'pull_request',\n deliveryId: `poll:${repository.id}:pull-request:${pullRequestNumber}:${state.createdAt}`,\n payload: {\n action: 'opened',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.author ?? '__unknown__' },\n pull_request: pullRequestPayload(pullRequestNumber, state),\n },\n };\n}\n\nexport function reconciledClosedEvent(\n repository: ReconcileRepository,\n pullRequestNumber: number,\n state: ReconcilePullRequestState,\n): ParsedGithubWebhook {\n const outcome = state.merged ? 'merged' : 'closed';\n return {\n event: 'pull_request',\n // Stable per (repository, PR, outcome): the ingress dedupe makes repeat\n // reconcile cycles replay instead of re-committing decisions.\n deliveryId: `reconcile:${repository.id}:pull-request:${pullRequestNumber}:${outcome}`,\n payload: {\n action: 'closed',\n installation: { id: repository.installationId },\n repository: { id: repository.id, full_name: repository.fullName },\n sender: { login: state.mergedBy ?? 'github' },\n pull_request: pullRequestPayload(pullRequestNumber, { ...state, state: 'closed' }),\n },\n };\n}\n\nfunction pullRequestPayload(pullRequestNumber: number, state: ReconcilePullRequestState): Record<string, unknown> {\n const payload: Record<string, unknown> = {\n number: pullRequestNumber,\n title: state.title,\n html_url: state.url,\n state: state.state,\n draft: state.draft,\n merged: state.merged,\n assignees: githubUsers(state.assignees),\n requested_reviewers: githubUsers(state.requestedReviewers),\n labels: githubLabels(state.labels),\n head: { ref: state.headBranch },\n base: { ref: state.baseBranch },\n };\n if (state.createdAt) payload.created_at = state.createdAt;\n if (state.author) payload.user = { login: state.author };\n return payload;\n}\n\nfunction githubUsers(logins: readonly string[] = []): Array<{ login: string }> {\n return logins.map(login => ({ login }));\n}\n\nfunction githubLabels(names: readonly string[] = []): Array<{ name: string }> {\n return names.map(name => ({ name }));\n}\n\nfunction reconciledPullRequestMetadata(\n state: ReconcilePullRequestState,\n reconciliation: 'clear' | 'settled',\n authorTrusted?: boolean,\n): Record<string, unknown> {\n return {\n state: state.state,\n draft: state.draft,\n merged: state.merged,\n ...(state.author ? { author: state.author } : {}),\n ...(state.assignees ? { assignees: state.assignees } : {}),\n ...(state.requestedReviewers ? { requestedReviewers: state.requestedReviewers } : {}),\n ...(state.labels ? { labels: state.labels } : {}),\n ...(authorTrusted === undefined ? {} : { authorTrusted }),\n [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]:\n reconciliation === 'settled' ? (state.merged ? 'merged' : 'closed') : null,\n };\n}\n\nfunction reconciledPullRequestOutcome(metadata: Record<string, unknown>): 'merged' | 'closed' | undefined {\n if (metadata.state !== 'closed' || typeof metadata.merged !== 'boolean') return undefined;\n return metadata.merged ? 'merged' : 'closed';\n}\n\n/**\n * The webhook handler retires subscriptions itself; the sweep replays only the\n * rules ingress, so without this the thread's PR chip and the workspace row\n * stay `open` forever on a deployment GitHub cannot reach.\n */\nasync function retireReconciledSubscriptions(\n storage: IntegrationStorageHandle,\n repository: ReconcileRepository,\n pullRequestNumber: number,\n merged: boolean,\n): Promise<void> {\n const target = changeRequestTargetKey({\n installationExternalId: String(repository.installationId),\n repositoryExternalId: String(repository.id),\n changeRequestId: String(pullRequestNumber),\n });\n const rows = await storage.subscriptions.listByTarget(target);\n await Promise.all(\n rows\n .filter(row => row.status === 'open')\n .map(row => storage.subscriptions.updateStatus(row.id, merged ? 'merged' : 'closed')),\n );\n}\n\n/**\n * State-based safety net for merge signals: webhooks and event-log tailing\n * can miss a merge (cursor gaps, downtime, terminally failed decisions), so\n * this sweep compares still-open PR cards against actual GitHub state and\n * replays the merge through the normal rules ingress when they disagree.\n */\nexport function createGithubPullRequestReconciler(\n options: GithubRulesOptions,\n fetchPullRequest: GithubPullRequestFetcher,\n): GithubPullRequestReconciler {\n const rules = new GithubRules(options);\n return async repositories => {\n const summary: ReconcileSweepSummary = {\n repositories: 0,\n checked: 0,\n merged: 0,\n closed: 0,\n failed: 0,\n errors: [],\n };\n const recordFailure = (repository: ReconcileRepository, error: unknown, pullRequestNumber?: number) => {\n summary.failed += 1;\n if (summary.errors.length < RECONCILE_ERROR_SAMPLE_LIMIT) {\n summary.errors.push({\n repository: repository.fullName,\n ...(pullRequestNumber === undefined ? {} : { pullRequestNumber }),\n error: error instanceof Error ? error.message : String(error),\n });\n }\n };\n // An installation can expose hundreds of repositories; only the ones\n // actually linked to a factory project can have cards to reconcile, so\n // scope the sweep to those up front instead of probing each repository.\n const configured = new Set(\n (await options.sourceControl.projectRepositories.listConfiguredExternalKeys()).map(\n key => `${key.installationExternalId}\\u0000${key.repositoryExternalId}`,\n ),\n );\n const scoped = repositories.filter(repository =>\n configured.has(`${repository.installationId}\\u0000${repository.id}`),\n );\n summary.repositories = scoped.length;\n for (const repository of scoped) {\n // One broken repository (or a failing token exchange for its\n // installation) must not abort the sweep for the others.\n let cardsByNumber: Map<number, WorkItemRow[]>;\n let unanswered: Array<{ item: WorkItemRow; author: string }>;\n try {\n const projects = await options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: String(repository.installationId),\n repositoryExternalId: String(repository.id),\n });\n if (projects.length === 0) continue;\n cardsByNumber = new Map<number, WorkItemRow[]>();\n unanswered = [];\n for (const project of projects) {\n const items = await options.storage.list({\n orgId: project.orgId,\n factoryProjectId: project.factoryProjectId,\n });\n for (const item of items) {\n const unansweredAuthor = authorAwaitingTrust(item, repository);\n if (unansweredAuthor) unanswered.push({ item, author: unansweredAuthor });\n const pullRequestNumber = reconcilablePullRequestNumber(item, repository);\n if (!pullRequestNumber) continue;\n const metadata = item.metadata ?? {};\n const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];\n const reconciledOutcome = reconciledPullRequestOutcome(metadata);\n if (\n workItemPhaseSemantics(options.boards, item)?.kind === 'terminal' &&\n reconciledOutcome !== undefined &&\n reconciliation === reconciledOutcome\n ) {\n continue;\n }\n const cards = cardsByNumber.get(pullRequestNumber) ?? [];\n cards.push(item);\n cardsByNumber.set(pullRequestNumber, cards);\n }\n }\n } catch (error) {\n recordFailure(repository, error);\n continue;\n }\n const authorTrust = sweepTrustLookup(options.github, repository);\n for (const { item, author } of unanswered) {\n try {\n await options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { authorTrusted: await authorTrust(author) } },\n });\n } catch (error) {\n recordFailure(repository, error);\n }\n }\n for (const [pullRequestNumber, cards] of cardsByNumber) {\n try {\n const state = await fetchPullRequest({\n installationId: repository.installationId,\n repository: repository.fullName,\n number: pullRequestNumber,\n });\n summary.checked += 1;\n if (!state) continue;\n // Re-stamped on every sweep so revoked write access reads untrusted\n // within one cycle; a failed lookup keeps the last stamp and retries.\n let authorTrusted: boolean | undefined;\n if (state.author !== undefined) {\n try {\n authorTrusted = await authorTrust(state.author);\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n for (const card of cards) {\n if (state.state === 'closed') continue;\n const metadata = card.metadata ?? {};\n const statusChanged =\n metadata.state !== state.state || metadata.draft !== state.draft || metadata.merged !== state.merged;\n const authorChanged = state.author !== undefined && metadata.author !== state.author;\n const assigneesChanged = !sameStrings(metadata.assignees, state.assignees);\n const reviewersChanged = !sameStrings(metadata.requestedReviewers, state.requestedReviewers);\n const labelsChanged = !sameStrings(metadata.labels, state.labels);\n const trustStale = authorTrusted !== undefined && metadata.authorTrusted !== authorTrusted;\n const metadataChanged =\n statusChanged || authorChanged || assigneesChanged || reviewersChanged || labelsChanged || trustStale;\n const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];\n if (!metadataChanged && reconciliation !== 'merged' && reconciliation !== 'closed') continue;\n try {\n await options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: {\n metadata: reconciledPullRequestMetadata(state, 'clear', trustStale ? authorTrusted : undefined),\n },\n });\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n if (state.state !== 'closed') continue;\n const cleanupFailures = new Set<string>();\n for (const card of cards) {\n if (workItemPhaseSemantics(options.boards, card)?.kind !== 'terminal') continue;\n try {\n await options.storage.supersedeDecisionsForWorkItem({\n orgId: card.orgId,\n factoryProjectId: card.factoryProjectId,\n workItemId: card.id,\n supersededAt: new Date(),\n });\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n cleanupFailures.add(card.id);\n }\n }\n await rules.ingest(reconciledClosedEvent(repository, pullRequestNumber, state));\n await retireReconciledSubscriptions(options.integrationStorage, repository, pullRequestNumber, state.merged);\n for (const card of cards) {\n if (cleanupFailures.has(card.id)) continue;\n const trustStale = authorTrusted !== undefined && (card.metadata ?? {}).authorTrusted !== authorTrusted;\n try {\n await options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: {\n metadata: reconciledPullRequestMetadata(state, 'settled', trustStale ? authorTrusted : undefined),\n },\n });\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n if (state.merged) summary.merged += 1;\n else summary.closed += 1;\n } catch (error) {\n recordFailure(repository, error, pullRequestNumber);\n }\n }\n }\n return summary;\n };\n}\n\nexport function githubRulesOptions(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n): GithubRulesOptions | undefined {\n if (!context.runtime) return undefined;\n return {\n github,\n sourceControl: context.storage.sourceControl,\n integrationStorage: context.storage.generic,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n configVersion: context.runtime.configVersion,\n boards: context.runtime.boards,\n intake: context.storage.intake,\n ...(context.hooks?.onRepositoryPush ? { onRepositoryPush: context.hooks.onRepositoryPush } : {}),\n };\n}\n\nexport function attachGithubRules(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n): ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined {\n const options = githubRulesOptions(github, context);\n if (!options) return undefined;\n const rules = new GithubRules(options);\n return event => rules.ingest(event);\n}\n\nexport function attachGithubReconciler(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n fetchPullRequest: GithubPullRequestFetcher,\n): GithubPullRequestReconciler | undefined {\n const options = githubRulesOptions(github, context);\n if (!options) return undefined;\n return createGithubPullRequestReconciler(options, fetchPullRequest);\n}\n"],"mappings":"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"}
@@ -100,6 +100,31 @@ export interface SessionBranchOptions {
100
100
  }
101
101
  /** Check out a session's branch inside its isolated repository clone. */
102
102
  export declare function checkoutSessionBranch(sandbox: ExecutableSandbox, workdir: string, options: SessionBranchOptions): Promise<void>;
103
+ export interface SyncEnvironmentRepositoryOptions {
104
+ /** The session branch to resume when the remote already carries it. */
105
+ branch: string;
106
+ /** The repository's default branch; new session branches start at its remote tip. */
107
+ defaultBranch: string;
108
+ token: string;
109
+ repoFullName: string;
110
+ cloneUrl?: string;
111
+ authUsername?: string;
112
+ }
113
+ export interface EnvironmentSyncResult {
114
+ outcome: 'resumed' | 'created' | 'kept';
115
+ /** The branch the checkout ends on; `null` when it stays detached. */
116
+ branch: string | null;
117
+ }
118
+ /**
119
+ * Bring a secondary environment repository onto the session branch at
120
+ * session start. The primary repository keeps {@link checkoutSessionBranch};
121
+ * every other repository follows a conservative rule: resume the session
122
+ * branch when the remote or the local clone has it, otherwise create it from
123
+ * the default branch tip (a detached HEAD, the template image pinned at its
124
+ * build commit, is moved there first). A checkout that sits on any other
125
+ * branch is left alone: it may carry local-only commits.
126
+ */
127
+ export declare function syncEnvironmentRepository(sandbox: ExecutableSandbox, workdir: string, options: SyncEnvironmentRepositoryOptions): Promise<EnvironmentSyncResult>;
103
128
  /** Refresh an existing GitLab review checkout without exposing its credential to the agent. */
104
129
  export declare function refreshMergeRequestCheckout(sandbox: ExecutableSandbox, workdir: string, input: {
105
130
  branch: string;
@@ -129,6 +154,16 @@ export declare function resolveGitIdentity(identity: GitIdentity): {
129
154
  };
130
155
  /** Configure repository-local commit identity through direct Git argv calls. */
131
156
  export declare function configureGitIdentity(sandbox: ExecutableSandbox, workdir: string, identity: GitIdentity): Promise<void>;
157
+ /** Whether the checkout has a local `refs/heads/<branch>`. */
158
+ export declare function hasLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise<boolean>;
159
+ /**
160
+ * Make sure the checkout has a local `<branch>`. Where it was never created,
161
+ * create it at `HEAD` and switch to it, so the agent's next commits land on
162
+ * the branch every push sends; the index and working tree carry over.
163
+ */
164
+ export declare function ensureLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise<void>;
165
+ /** Whether `workdir` is a checkout of the repository at `cloneUrl`. */
166
+ export declare function isCheckoutOf(sandbox: ExecutableSandbox, workdir: string, cloneUrl: string): Promise<boolean>;
132
167
  /**
133
168
  * Push a branch back to GitHub with credentials scoped to the Git process.
134
169
  * The token never enters argv, a repository URL, or persistent configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/sandbox.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/sandbox.ts"],"names":[],"mappings":"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"}