@mastra/factory 0.12.0-alpha.1 → 0.12.0-alpha.2

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 (39) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/integrations/github/issue-reconciler.d.ts.map +1 -1
  3. package/dist/integrations/github/issue-reconciler.js +22 -14
  4. package/dist/integrations/github/issue-reconciler.js.map +1 -1
  5. package/dist/integrations/github/rules.d.ts +6 -0
  6. package/dist/integrations/github/rules.d.ts.map +1 -1
  7. package/dist/integrations/github/rules.js +58 -12
  8. package/dist/integrations/github/rules.js.map +1 -1
  9. package/dist/routes/surface.d.ts.map +1 -1
  10. package/dist/routes/surface.js +4 -3
  11. package/dist/routes/surface.js.map +1 -1
  12. package/dist/routes/work-items.d.ts.map +1 -1
  13. package/dist/routes/work-items.js +11 -1
  14. package/dist/routes/work-items.js.map +1 -1
  15. package/dist/rules/defaults.d.ts.map +1 -1
  16. package/dist/rules/defaults.js +20 -8
  17. package/dist/rules/defaults.js.map +1 -1
  18. package/dist/rules/dispatcher.d.ts.map +1 -1
  19. package/dist/rules/dispatcher.js +24 -10
  20. package/dist/rules/dispatcher.js.map +1 -1
  21. package/dist/rules/processor.d.ts.map +1 -1
  22. package/dist/rules/processor.js +2 -3
  23. package/dist/rules/processor.js.map +1 -1
  24. package/dist/rules/transition-service.d.ts +2 -2
  25. package/dist/rules/transition-service.d.ts.map +1 -1
  26. package/dist/rules/transition-service.js +33 -12
  27. package/dist/rules/transition-service.js.map +1 -1
  28. package/dist/rules/types.d.ts +27 -1
  29. package/dist/rules/types.d.ts.map +1 -1
  30. package/dist/rules/types.js +37 -1
  31. package/dist/rules/types.js.map +1 -1
  32. package/dist/rules/validation.d.ts.map +1 -1
  33. package/dist/rules/validation.js +3 -1
  34. package/dist/rules/validation.js.map +1 -1
  35. package/dist/storage/domains/work-items/base.d.ts +4 -2
  36. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  37. package/dist/storage/domains/work-items/base.js +35 -27
  38. package/dist/storage/domains/work-items/base.js.map +1 -1
  39. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","names":["#ingestProject","#relatedItem","#isFactoryLogin","#linkedClosureItem","#resolveItem"],"sources":["../../../src/integrations/github/rules.ts"],"sourcesContent":["import { resolveFactoryGithubRule } from '../../rules/resolve.js';\nimport type {\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n FactoryRules,\n} from '../../rules/types.js';\nimport { isTerminalFactoryRuleStage } from '../../rules/types.js';\nimport { validateFactoryRuleDecisions } from '../../rules/validation.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 { FACTORY_PULL_REQUEST_RECONCILIATION_KEY } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport type { GithubAppIdentity } from './app-identity.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 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 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 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 */\nfunction 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\nasync function githubActor(\n github: GithubRulesIntegration,\n input: { installationId: number; repository: string; login: string; factoryAuthored: boolean },\n): Promise<FactoryRuleActor> {\n let trusted = false;\n try {\n const permission = await github.getRepositoryCollaboratorPermission(\n input.installationId,\n input.repository,\n input.login,\n );\n trusted = permission !== undefined && TRUSTED_PERMISSIONS.has(permission);\n } catch {\n trusted = false;\n }\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(data: Record<string, unknown> | undefined): FactoryPullRequestProvenanceData | null {\n if (!data || data.kind !== 'factory-pr-provenance' || typeof data.workItemId !== 'string') return null;\n return { kind: 'factory-pr-provenance', workItemId: data.workItemId };\n}\n\nexport interface GithubRulesIntegration {\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 rules: FactoryRules;\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 const identity = this.options.github.identity;\n if (identity?.known) return identity.matches(login);\n const slug = this.options.github.slug?.trim();\n if (!slug || !login) return false;\n return login.toLowerCase() === `${slug.toLowerCase()}[bot]`;\n }\n\n async ingest(parsed: ParsedGithubWebhook): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> {\n const event = eventName(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 || !installationId || !repositoryId || !repositoryName || !login) return { status: 'ignored' };\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 await this.#ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, 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(subscription => subscription.orgId === project.orgId)?.data,\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 // 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 = reviewRequested || event === 'pullRequestUpdated';\n const requestedReviewer = string(object(parsed.payload.requested_reviewer)?.login);\n const relatedItem = 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 && !reviewRequested,\n );\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 ): 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 ruleSetVersion: this.options.rules.version,\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 metadata: item.metadata,\n },\n board: item.externalSource?.type === 'pull-request' ? ('review' as const) : ('work' as const),\n itemRevision: item.revision,\n }\n : {}),\n event,\n deliveryId: parsed.deliveryId,\n factory: { createdAt: factoryProject.createdAt.toISOString() },\n repository: { id: repositoryId, fullName: repositoryName },\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 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 ...(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 },\n }\n : {}),\n };\n\n const rule = resolveFactoryGithubRule(this.options.rules, event);\n let decision: FactoryRuleDecision | void;\n let decisions: Record<string, unknown>[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : undefined;\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n decisions = validateFactoryRuleDecisions([decision]).map(entry => ({ ...entry }));\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 ruleSetVersion: this.options.rules.version,\n expectedRevision: item?.revision ?? null,\n actor: { ...actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n return { status: committed.status };\n };\n\n const deliveryIdentity = `${installationId}:${parsed.deliveryId}`;\n const primary = await evaluate(relatedItem, deliveryIdentity);\n // A merged pull request is the one event both linked cards need: the\n // Review card has to close, and the Work item that wrote the code has to\n // assess whether it is finished. Resolution binds the delivery to whichever\n // card it matched first, so evaluate the other one too — under an identity\n // suffixed with its id, because ingress identities are the replay key and\n // reusing the delivery's own would drop this evaluation as a duplicate.\n const linked =\n event === 'pullRequestMerged' && relatedItem && pullRequestNumber\n ? await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n relatedItem,\n )\n : undefined;\n if (!linked) return primary;\n const secondary = await evaluate(linked, `${deliveryIdentity}:${linked.id}`);\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 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\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: (state.assignees ?? []).map(login => ({ login })),\n },\n },\n };\n}\n\nexport function reconciledClosedEvent(\n repository: ReconcileRepository,\n pullRequestNumber: number,\n state: ReconcilePullRequestState,\n): ParsedGithubWebhook {\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}:${state.merged ? 'merged' : 'closed'}`,\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: {\n number: pullRequestNumber,\n title: state.title,\n html_url: state.url,\n ...(state.createdAt ? { created_at: state.createdAt } : {}),\n state: 'closed',\n draft: state.draft,\n merged: state.merged,\n assignees: (state.assignees ?? []).map(login => ({ login })),\n requested_reviewers: (state.requestedReviewers ?? []).map(login => ({ login })),\n labels: (state.labels ?? []).map(name => ({ name })),\n head: { ref: state.headBranch },\n base: { ref: state.baseBranch },\n },\n },\n };\n}\n\nfunction reconciledPullRequestMetadata(\n state: ReconcilePullRequestState,\n reconciliation: 'clear' | 'settled',\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 [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 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 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 stage = item.stages[0];\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 (stage === 'done' || stage === 'canceled') &&\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 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 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 metadataChanged =\n statusChanged || authorChanged || assigneesChanged || reviewersChanged || labelsChanged;\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: { metadata: reconciledPullRequestMetadata(state, 'clear') },\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 (!isTerminalFactoryRuleStage(card.stages)) 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 try {\n await options.storage.update({\n orgId: card.orgId,\n id: card.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: reconciledPullRequestMetadata(state, 'settled') },\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.rules) return undefined;\n return {\n github,\n sourceControl: context.storage.sourceControl,\n integrationStorage: context.storage.generic,\n projects: context.storage.projects,\n storage: context.rules.workItems,\n rules: context.rules.config,\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":["#ingestProject","#relatedItem","#isFactoryLogin","#linkedClosureItem","#resolveItem"],"sources":["../../../src/integrations/github/rules.ts"],"sourcesContent":["import { resolveFactoryGithubRule } from '../../rules/resolve.js';\nimport type {\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n FactoryRules,\n} from '../../rules/types.js';\nimport { isTerminalFactoryRuleStage } from '../../rules/types.js';\nimport { validateFactoryRuleDecisions } from '../../rules/validation.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 { FACTORY_PULL_REQUEST_RECONCILIATION_KEY } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport type { GithubAppIdentity } from './app-identity.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 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 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 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 */\nfunction 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 = !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(data: Record<string, unknown> | undefined): FactoryPullRequestProvenanceData | null {\n if (!data || data.kind !== 'factory-pr-provenance' || typeof data.workItemId !== 'string') return null;\n return { kind: 'factory-pr-provenance', workItemId: data.workItemId };\n}\n\nexport interface GithubRulesIntegration {\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 rules: FactoryRules;\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 const identity = this.options.github.identity;\n if (identity?.known) return identity.matches(login);\n const slug = this.options.github.slug?.trim();\n if (!slug || !login) return false;\n return login.toLowerCase() === `${slug.toLowerCase()}[bot]`;\n }\n\n async ingest(parsed: ParsedGithubWebhook): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing' }> {\n const event = eventName(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 || !installationId || !repositoryId || !repositoryName || !login) return { status: 'ignored' };\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 await this.#ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, 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(subscription => subscription.orgId === project.orgId)?.data,\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 // 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 = reviewRequested || event === 'pullRequestUpdated';\n const requestedReviewer = string(object(parsed.payload.requested_reviewer)?.login);\n const relatedItem = 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 && !reviewRequested,\n );\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 ): 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 ruleSetVersion: this.options.rules.version,\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 metadata: item.metadata,\n },\n board: item.externalSource?.type === 'pull-request' ? ('review' as const) : ('work' as const),\n itemRevision: item.revision,\n }\n : {}),\n event,\n deliveryId: parsed.deliveryId,\n factory: { createdAt: factoryProject.createdAt.toISOString() },\n repository: { id: repositoryId, fullName: repositoryName },\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 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 ...(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 },\n }\n : {}),\n };\n\n const rule = resolveFactoryGithubRule(this.options.rules, event);\n let decision: FactoryRuleDecision | void;\n let decisions: Record<string, unknown>[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : undefined;\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n decisions = validateFactoryRuleDecisions([decision]).map(entry => ({ ...entry }));\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 ruleSetVersion: this.options.rules.version,\n expectedRevision: item?.revision ?? null,\n actor: { ...actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n return { status: committed.status };\n };\n\n const deliveryIdentity = `${installationId}:${parsed.deliveryId}`;\n const primary = await evaluate(relatedItem, deliveryIdentity);\n // A merged pull request is the one event both linked cards need: the\n // Review card has to close, and the Work item that wrote the code has to\n // assess whether it is finished. Resolution binds the delivery to whichever\n // card it matched first, so evaluate the other one too — under an identity\n // suffixed with its id, because ingress identities are the replay key and\n // reusing the delivery's own would drop this evaluation as a duplicate.\n const linked =\n event === 'pullRequestMerged' && relatedItem && pullRequestNumber\n ? await this.#linkedClosureItem(\n project.orgId,\n project.factoryProjectId,\n repositoryId,\n repositoryName,\n pullRequestNumber,\n relatedItem,\n )\n : undefined;\n if (!linked) return primary;\n const secondary = await evaluate(linked, `${deliveryIdentity}:${linked.id}`);\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 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\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: (state.assignees ?? []).map(login => ({ login })),\n },\n },\n };\n}\n\nexport function reconciledClosedEvent(\n repository: ReconcileRepository,\n pullRequestNumber: number,\n state: ReconcilePullRequestState,\n): ParsedGithubWebhook {\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}:${state.merged ? 'merged' : 'closed'}`,\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: {\n number: pullRequestNumber,\n title: state.title,\n html_url: state.url,\n ...(state.createdAt ? { created_at: state.createdAt } : {}),\n state: 'closed',\n draft: state.draft,\n merged: state.merged,\n assignees: (state.assignees ?? []).map(login => ({ login })),\n requested_reviewers: (state.requestedReviewers ?? []).map(login => ({ login })),\n labels: (state.labels ?? []).map(name => ({ name })),\n head: { ref: state.headBranch },\n base: { ref: state.baseBranch },\n },\n },\n };\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 stage = item.stages[0];\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 (stage === 'done' || stage === 'canceled') &&\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: { metadata: reconciledPullRequestMetadata(state, 'clear', trustStale ? authorTrusted : undefined) },\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 (!isTerminalFactoryRuleStage(card.stages)) 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: { metadata: reconciledPullRequestMetadata(state, 'settled', trustStale ? authorTrusted : undefined) },\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.rules) return undefined;\n return {\n github,\n sourceControl: context.storage.sourceControl,\n integrationStorage: context.storage.generic,\n projects: context.storage.projects,\n storage: context.rules.workItems,\n rules: context.rules.config,\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 +1 @@
1
- {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { factoryRuleStage } from "../rules/types.js";
1
+ import { factoryLaneForRole, factoryRuleStage } from "../rules/types.js";
2
2
  import { getGithubFeatureDiagnostics } from "../integrations/github/config.js";
3
3
  import { MaterializeError } from "../integrations/github/sandbox.js";
4
4
  import { invalidateCustomProvidersSnapshots } from "./custom-provider-source.js";
@@ -84,8 +84,9 @@ async function prepareFactoryRuleBinding(github, coordinator, projects, input) {
84
84
  source: workItemBranchSource(input.item.externalSource),
85
85
  metadata: input.item.metadata
86
86
  });
87
- const destinationStage = factoryRuleStage(input.item.stages);
88
- if (!destinationStage) throw new FactoryDispatchError("unsupported_provider_item", "Factory skill invocation requires one exclusive board stage.");
87
+ const currentStage = factoryRuleStage(input.item.stages);
88
+ const destinationStage = currentStage === "intake" ? factoryLaneForRole(input.role) : currentStage;
89
+ if (!destinationStage) throw new FactoryDispatchError("unsupported_provider_item", `Factory skill invocation has no destination lane (role "${input.role}", stages [${input.item.stages.join(", ")}]).`);
89
90
  const repositorySlug = typeof input.item.metadata?.repository === "string" ? input.item.metadata.repository : void 0;
90
91
  const preparedSession = await ensureFactorySourceSession({
91
92
  sourceControl: github.sourceControlStorage,
@@ -1 +1 @@
1
- {"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRules } from '../rules/types.js';\nimport { factoryRuleStage } from '../rules/types.js';\nimport type { MastraFactorySandboxConfig } from '../sandbox/session-sandbox.js';\nimport {\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactoryDefaultModelId,\n} from '../session/factory-session.js';\nimport { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlStorage,\n} from '../storage/domains/source-control/base.js';\nimport type { FactoryDispatchFailureCode, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource } from '../work-item-branch.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n rules: FactoryRules;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n github: GithubIntegration,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n try {\n const branch = workItemBranch({\n id: input.item.id,\n source: workItemBranchSource(input.item.externalSource),\n metadata: input.item.metadata,\n });\n const destinationStage = factoryRuleStage(input.item.stages);\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n 'Factory skill invocation requires one exclusive board stage.',\n );\n }\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n const preparedSession = await ensureFactorySourceSession({\n sourceControl: github.sourceControlStorage,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n // A human-approved proposal has an interactive user: attribute the run to\n // the approver, not the repo connector.\n attributeToUserId: input.record.approvedBy ?? undefined,\n });\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: `${input.role === 'review' ? 'PR' : 'Issue'}: ${input.item.title}`,\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n rules: FactoryRules;\n factoryReady: boolean;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n ...(deps.factoryReady ? { rules: { config: deps.rules, workItems: deps.domains.workItems } } : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status` and `/web/linear/status` unconditionally, so when an\n * integration is absent (or not ready) the status contract must still hold.\n * Unknown custom ids get no stub — the SPA doesn't poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/model-pack/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({ rules: deps.rules, storage: deps.domains.workItems }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n githubIntegration?.sourceControlStorage,\n deps.domains.memorySettings,\n new FactoryFeedReader(deps.domains.comments),\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(githubIntegration\n ? {\n prepareBinding: (input: FactoryBindingPreparationInput) =>\n prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input),\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: deps.sourceControlStorage.forIntegration('github').sessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelPacks: deps.domains.modelPacks,\n sourceControlSessions: deps.sourceControlStorage.forIntegration('github').sessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...integrationRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: new LiveSessions(deps.controller),\n }).routes()\n : []),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRules } from '../rules/types.js';\nimport { factoryLaneForRole, factoryRuleStage } from '../rules/types.js';\nimport type { MastraFactorySandboxConfig } from '../sandbox/session-sandbox.js';\nimport {\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactoryDefaultModelId,\n} from '../session/factory-session.js';\nimport { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlStorage,\n} from '../storage/domains/source-control/base.js';\nimport type { FactoryDispatchFailureCode, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource } from '../work-item-branch.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n rules: FactoryRules;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n github: GithubIntegration,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n try {\n const branch = workItemBranch({\n id: input.item.id,\n source: workItemBranchSource(input.item.externalSource),\n metadata: input.item.metadata,\n });\n // Only the Intake exit derives a lane from the role: roles don't own lanes,\n // and the Done close-out running in the triage seat must not drag the card back.\n const currentStage = factoryRuleStage(input.item.stages);\n const destinationStage = currentStage === 'intake' ? factoryLaneForRole(input.role) : currentStage;\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n `Factory skill invocation has no destination lane (role \"${input.role}\", stages [${input.item.stages.join(', ')}]).`,\n );\n }\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n const preparedSession = await ensureFactorySourceSession({\n sourceControl: github.sourceControlStorage,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n // A human-approved proposal has an interactive user: attribute the run to\n // the approver, not the repo connector.\n attributeToUserId: input.record.approvedBy ?? undefined,\n });\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: `${input.role === 'review' ? 'PR' : 'Issue'}: ${input.item.title}`,\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n rules: FactoryRules;\n factoryReady: boolean;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n ...(deps.factoryReady ? { rules: { config: deps.rules, workItems: deps.domains.workItems } } : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status` and `/web/linear/status` unconditionally, so when an\n * integration is absent (or not ready) the status contract must still hold.\n * Unknown custom ids get no stub — the SPA doesn't poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/model-pack/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({ rules: deps.rules, storage: deps.domains.workItems }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n githubIntegration?.sourceControlStorage,\n deps.domains.memorySettings,\n new FactoryFeedReader(deps.domains.comments),\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(githubIntegration\n ? {\n prepareBinding: (input: FactoryBindingPreparationInput) =>\n prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input),\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: deps.sourceControlStorage.forIntegration('github').sessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelPacks: deps.domains.modelPacks,\n sourceControlSessions: deps.sourceControlStorage.forIntegration('github').sessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...integrationRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: new LiveSessions(deps.controller),\n }).routes()\n : []),\n ];\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"work-items.d.ts","sourceRoot":"","sources":["../../src/routes/work-items.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EACV,uBAAuB,EAGxB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAA4B,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAGzG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAElF,OAAO,KAAK,EACV,mBAAmB,EAInB,mBAAmB,EAKnB,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAQ/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,KAAK,EAAE,YAAY,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,sBAAsB,CAAC;IACjC,kDAAkD;IAClD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,EAAE,uBAAuB,CAAC;IAClC,iDAAiD;IACjD,WAAW,EAAE,kBAAkB,CAAC;IAChC,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,GAAG,gBAAgB,CAAC,CAAC;IACpF,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IAC5D,wFAAwF;IACxF,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;CAC/C;AAsGD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CA2B7E;AAED,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAgC7E;AA2LD,qBAAa,cAAe,SAAQ,KAAK,CAAC,kBAAkB,CAAC;;IAyJ3D,gEAAgE;IAChE,MAAM,IAAI,QAAQ,EAAE;CAgYrB"}
1
+ {"version":3,"file":"work-items.d.ts","sourceRoot":"","sources":["../../src/routes/work-items.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EACV,uBAAuB,EAGxB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAA4B,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAGzG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAElF,OAAO,KAAK,EACV,mBAAmB,EAInB,mBAAmB,EAKnB,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAQ/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,KAAK,EAAE,YAAY,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,sBAAsB,CAAC;IACjC,kDAAkD;IAClD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,EAAE,uBAAuB,CAAC;IAClC,iDAAiD;IACjD,WAAW,EAAE,kBAAkB,CAAC;IAChC,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,GAAG,gBAAgB,CAAC,CAAC;IACpF,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IAC5D,wFAAwF;IACxF,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;CAC/C;AAsGD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CA2B7E;AAED,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAgC7E;AAqMD,qBAAa,cAAe,SAAQ,KAAK,CAAC,kBAAkB,CAAC;;IAyJ3D,gEAAgE;IAChE,MAAM,IAAI,QAAQ,EAAE;CAgYrB"}
@@ -3,6 +3,7 @@ import { FACTORY_PULL_REQUEST_RECONCILIATION_KEY, FACTORY_RULE_MATERIALIZATION_K
3
3
  import { Route } from "./route.js";
4
4
  import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
5
5
  import { FactoryStartTransitionError } from "../rules/start-coordinator.js";
6
+ import { roleForStage } from "../rules/transition-service.js";
6
7
  import { thresholdsOrDefault } from "../storage/domains/queue-health/base.js";
7
8
  import { computeFactoryMetrics, parseMetricsRange } from "../storage/domains/work-items/metrics.js";
8
9
  import { factoryDecisionType } from "./attention-providers.js";
@@ -263,13 +264,22 @@ function parseDecisionCursor(raw) {
263
264
  return;
264
265
  }
265
266
  }
267
+ /** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */
268
+ function summaryRole(decision) {
269
+ if (typeof decision.role === "string") return decision.role.slice(0, 32);
270
+ if (decision.type !== "transition") return null;
271
+ const board = decision.board;
272
+ const stage = decision.stage;
273
+ if (board !== "work" && board !== "review" || !isFactoryRuleStage(stage)) return null;
274
+ return roleForStage(board, stage);
275
+ }
266
276
  function decisionSummary(decision) {
267
277
  return {
268
278
  id: decision.id,
269
279
  evaluationId: decision.evaluationId,
270
280
  workItemId: decision.workItemId,
271
281
  type: factoryDecisionType(decision),
272
- role: typeof decision.decision.role === "string" ? decision.decision.role.slice(0, 32) : null,
282
+ role: summaryRole(decision.decision),
273
283
  status: decision.status,
274
284
  attempts: decision.attempts,
275
285
  failureOccurrence: decision.failureOccurrence,