import type { BoardRegistry } from '../../boards/index.js'; import type { IntakeStorage } from '../../storage/domains/intake/base.js'; import type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js'; import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js'; import type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js'; import type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js'; import type { IntegrationContext, IntegrationHooks } from '../base.js'; import type { GithubAppIdentity } from './app-identity.js'; import type { GithubEventRules } from './default-rules.js'; import type { GithubRepositoryPermission } from './integration.js'; import type { ParsedGithubWebhook } from './webhook.js'; export declare function cardBelongsToRepository(item: WorkItemRow, repositoryId: number, repositoryFullName: string): boolean; export declare function trustedCollaborator(github: GithubRulesIntegration, input: { installationId: number; repository: string; login: string; }): Promise; export declare function sweepTrustLookup(github: GithubRulesIntegration, repository: ReconcileRepository): (login: string) => Promise; export interface GithubRulesIntegration { readonly rules: GithubEventRules; readonly slug?: string; /** * Factory's own GitHub login, used to ignore its own writes. Optional because * not every integration can name itself; when absent, self-recognition falls * back to the configured slug and, failing that, to content Factory stamps * itself (see `FACTORY_TRIAGE_COMMENT_MARKER`). */ readonly identity?: GithubAppIdentity; getRepositoryCollaboratorPermission(installationId: number, repoFullName: string, username: string): Promise; } export interface GithubRulesOptions { github: GithubRulesIntegration; sourceControl: SourceControlStorageHandle; /** Integration-scoped storage; provenance rows are validated at read. */ integrationStorage: IntegrationStorageHandle; projects: FactoryProjectsStorage; storage: WorkItemsStorage; configVersion: string; boards: BoardRegistry; /** * Decides which installed board an issue lands on: a label route first, then the repository's * intake source binding. Absent means Work. */ intake?: Pick; /** Hands a push to a linked repository off for an environment build. */ onRepositoryPush?: IntegrationHooks['onRepositoryPush']; } /** * Whether a login is Factory itself. Prefers the resolved identity, which is * observed from Factory's own writes, and falls back to the configured slug. */ export declare function isFactoryGithubLogin(github: Pick, login: string | null | undefined): boolean; export declare class GithubRules { #private; private readonly options; constructor(options: GithubRulesOptions); ingest(parsed: ParsedGithubWebhook): Promise<{ status: 'ignored' | 'committed' | 'replayed' | 'missing'; }>; } export interface ReconcilePullRequestState { title: string; url: string; state: 'open' | 'closed'; draft: boolean; merged: boolean; assignees?: string[]; requestedReviewers?: string[]; labels?: string[]; headBranch: string; baseBranch: string; author?: string; createdAt?: string; mergedBy?: string; } export interface PolledPullRequestState extends ReconcilePullRequestState { createdAt: string; } export type GithubPullRequestFetcher = (input: { installationId: number; repository: string; number: number; }) => Promise; export interface ReconcileIssueState { title: string; url: string; state: 'open' | 'closed'; /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */ stateReason?: string; assignees?: string[]; labels?: string[]; author?: string; createdAt?: string; updatedAt?: string; } export type GithubIssueFetcher = (input: { installationId: number; repository: string; number: number; }) => Promise; /** Lists a repository's open issues (pull requests excluded) for missed-open discovery. */ export type GithubOpenIssueLister = (input: { installationId: number; repository: string; }) => Promise>; export interface ReconcileRepository { id: number; fullName: string; installationId: number; } export interface ReconcileSweepSummary { /** Factory-configured repositories included in the sweep. */ repositories: number; /** PRs whose live state was fetched from GitHub. */ checked: number; /** Missed merges replayed through the rules ingress. */ merged: number; /** Missed closes-without-merge replayed through the rules ingress. */ closed: number; /** PRs/issues (or whole repositories) skipped because of an error. */ failed: number; /** Error samples with context, capped at {@link RECONCILE_ERROR_SAMPLE_LIMIT}. */ errors: Array<{ repository: string; pullRequestNumber?: number; issueNumber?: number; error: string; }>; } export type GithubPullRequestReconciler = (repositories: ReconcileRepository[]) => Promise; export declare const RECONCILE_ERROR_SAMPLE_LIMIT = 5; export declare function sameStrings(left: unknown, right: string[] | undefined): boolean; /** * Extracts the issue number a work item tracks, but only when the item * belongs to the given repository. Stricter than * {@link reconcilablePullRequestNumber}: a canonical key with no URL is only * trusted when the intake-stamped `githubRepositoryId` confirms the * repository, because the sweep initiates closes on its own. */ export declare function reconcilableIssueNumber(item: WorkItemRow, repository: ReconcileRepository): number | undefined; export declare function reconciledIssueClosedEvent(repository: ReconcileRepository, issueNumber: number, state: ReconcileIssueState): ParsedGithubWebhook; /** * The label-drift replay: an open issue whose labels changed without a * `labeled`/`unlabeled` webhook ever arriving (Factory was down, the delivery * was lost, or the label was applied by something other than a delivery the * webhook saw). Synthesized as an `opened` delivery so the deployment's * `issueOpened` rule decides placement, exactly as it does at arrival — one * source of placement policy for both. */ export declare function reconciledIssueRelabeledEvent(repository: ReconcileRepository, issueNumber: number, state: ReconcileIssueState): ParsedGithubWebhook; /** * The missed-open replay: an open issue with no Work card, because its * `opened` delivery never reached the rules ingress. Synthesized as the same * `opened` delivery so the deployment's `issueOpened` rule decides whether and * where it lands, exactly as it would have at arrival. The delivery id is * stable per issue so repeat sweeps dedupe at the ingress. */ export declare function reconciledIssueOpenedEvent(repository: ReconcileRepository, issueNumber: number, state: ReconcileIssueState): ParsedGithubWebhook; export declare function polledPullRequestEvent(repository: ReconcileRepository, pullRequestNumber: number, state: PolledPullRequestState): ParsedGithubWebhook; export declare function reconciledClosedEvent(repository: ReconcileRepository, pullRequestNumber: number, state: ReconcilePullRequestState): ParsedGithubWebhook; /** * State-based safety net for merge signals: webhooks and event-log tailing * can miss a merge (cursor gaps, downtime, terminally failed decisions), so * this sweep compares still-open PR cards against actual GitHub state and * replays the merge through the normal rules ingress when they disagree. */ export declare function createGithubPullRequestReconciler(options: GithubRulesOptions, fetchPullRequest: GithubPullRequestFetcher): GithubPullRequestReconciler; export declare function githubRulesOptions(github: GithubRulesIntegration, context: IntegrationContext): GithubRulesOptions | undefined; export declare function attachGithubRules(github: GithubRulesIntegration, context: IntegrationContext): ((event: ParsedGithubWebhook) => Promise) | undefined; export declare function attachGithubReconciler(github: GithubRulesIntegration, context: IntegrationContext, fetchPullRequest: GithubPullRequestFetcher): GithubPullRequestReconciler | undefined; //# sourceMappingURL=rules.d.ts.map