@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.
- package/CHANGELOG.md +16 -0
- package/dist/integrations/github/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/github/issue-reconciler.js +22 -14
- package/dist/integrations/github/issue-reconciler.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +6 -0
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +58 -12
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +4 -3
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +11 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +20 -8
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +24 -10
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +2 -3
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -2
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +33 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +27 -1
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +37 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +3 -1
- package/dist/rules/validation.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +4 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +35 -27
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @mastra/factory
|
|
2
2
|
|
|
3
|
+
## 0.12.0-alpha.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Board lanes now mean engagement: a card enters a working lane only when a run starts on it or a person moves it there, and resting a card takes the Factory's hand off it. ([#22531](https://github.com/mastra-ai/mastra/pull/22531))
|
|
8
|
+
|
|
9
|
+
Every GitHub issue and pull request arrives in Intake. Trust moved out of the column layout and onto the card: arrivals are stamped with whether the Factory may pick them up on its own, an **External** mark shows cards the execution gate treats as externally authored, and a card whose run the Factory would start shows that as a suggestion you can release with a click. Reviewing means a review is running — before, a maintainer's pull request was born there with nothing reviewing it.
|
|
10
|
+
|
|
11
|
+
Consent follows the same line. A person's drag into a working lane hands the Factory the work; any entry into Intake, Done or Canceled takes it back, whoever rested the card — a verdict, a mirrored close, a drag. The close-out run a resting transition queued still fires, pre-approved by the transition that committed it. An external event can no longer pull a rested card back into a working lane or start a run on it without a person's consent, and a card from an author without write access never self-starts — even armed, even with auto-run on. A GitHub card missing its trust stamp — created before stamps existed — fails closed and asks too. The reconcile sweep keeps the stamp current in both directions: it backfills missing stamps and withdraws trust from authors whose write access was revoked, each within one sweep cycle (a few minutes by default); the Factory's own pull requests count as trusted through their authorship.
|
|
12
|
+
|
|
13
|
+
A card parked in Intake offers Resume as its primary action, re-entering the deepest seat it used, and asking the card's agent in chat to resume does the same through the governed transition. Dragging a card out no longer opens a session just to say so: the stop notice reaches whichever session is live on the card, or nobody. A run landing its card in a lane no longer dispatches a second run.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- The reconcile sweep now records author trust for cards it had stopped visiting, so a board whose cards reached Done or Canceled before trust was recorded gets its answers on the next sweep instead of never. The board's External mark reads a recorded answer instead of the absence of one, so a card nobody was ever asked about is no longer labelled an outside contribution. The execution-consent gate is unchanged: a card with no recorded answer still asks for a person before it starts a run. ([#22644](https://github.com/mastra-ai/mastra/pull/22644))
|
|
18
|
+
|
|
3
19
|
## 0.12.0-alpha.1
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/issue-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/issue-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAUrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtH,MAAM,WAAW,2BAA2B;IAC1C,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5E;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,YAAY,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,2BAA2B,CAAC,CAAC;AAElH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kBAAkB,EAC3B,UAAU,EAAE,kBAAkB,GAC7B,qBAAqB,CA8HvB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,kBAAkB,EAC3B,UAAU,EAAE,kBAAkB,GAC7B,qBAAqB,GAAG,SAAS,CAKnC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GithubRules, githubRulesOptions, reconcilableIssueNumber, reconciledIssueClosedEvent, sameStrings } from "./rules.js";
|
|
1
|
+
import { GithubRules, githubRulesOptions, reconcilableIssueNumber, reconciledIssueClosedEvent, sameStrings, sweepTrustLookup } from "./rules.js";
|
|
2
2
|
//#region src/integrations/github/issue-reconciler.ts
|
|
3
3
|
function createGithubIssueReconciler(options, fetchIssue) {
|
|
4
4
|
const rules = new GithubRules(options);
|
|
@@ -11,8 +11,17 @@ function createGithubIssueReconciler(options, fetchIssue) {
|
|
|
11
11
|
failed: 0,
|
|
12
12
|
errors: []
|
|
13
13
|
};
|
|
14
|
+
const recordFailure = (repository, error, issueNumber) => {
|
|
15
|
+
summary.failed += 1;
|
|
16
|
+
if (summary.errors.length < 5) summary.errors.push({
|
|
17
|
+
repository: repository.fullName,
|
|
18
|
+
...issueNumber === void 0 ? {} : { issueNumber },
|
|
19
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20
|
+
});
|
|
21
|
+
};
|
|
14
22
|
for (const repository of repositories) {
|
|
15
23
|
summary.repositories += 1;
|
|
24
|
+
const authorTrust = sweepTrustLookup(options.github, repository);
|
|
16
25
|
try {
|
|
17
26
|
const projects = await options.sourceControl.projectRepositories.listByExternalRepository({
|
|
18
27
|
installationExternalId: String(repository.installationId),
|
|
@@ -59,34 +68,33 @@ function createGithubIssueReconciler(options, fetchIssue) {
|
|
|
59
68
|
...state.assignees === void 0 ? {} : { assignees: state.assignees },
|
|
60
69
|
...state.labels === void 0 ? {} : { labels: state.labels }
|
|
61
70
|
};
|
|
71
|
+
let authorTrusted;
|
|
72
|
+
if (state.author !== void 0) try {
|
|
73
|
+
authorTrusted = await authorTrust(state.author);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
recordFailure(repository, error, issueNumber);
|
|
76
|
+
}
|
|
62
77
|
for (const item of items) {
|
|
63
78
|
const current = item.metadata ?? {};
|
|
64
|
-
|
|
79
|
+
const trustStale = authorTrusted !== void 0 && current.authorTrusted !== authorTrusted;
|
|
80
|
+
if (!(trustStale || current.githubRepositoryId !== desiredMetadata.githubRepositoryId || current.githubIssueNumber !== desiredMetadata.githubIssueNumber || current.state !== desiredMetadata.state || state.author !== void 0 && current.author !== state.author || state.assignees !== void 0 && !sameStrings(current.assignees, state.assignees) || state.labels !== void 0 && !sameStrings(current.labels, state.labels))) continue;
|
|
65
81
|
await options.storage.update({
|
|
66
82
|
orgId: item.orgId,
|
|
67
83
|
id: item.id,
|
|
68
84
|
userId: "factory-rule-dispatcher",
|
|
69
85
|
patch: { metadata: {
|
|
70
86
|
...current,
|
|
71
|
-
...desiredMetadata
|
|
87
|
+
...desiredMetadata,
|
|
88
|
+
...trustStale ? { authorTrusted } : {}
|
|
72
89
|
} }
|
|
73
90
|
});
|
|
74
91
|
summary.updated += 1;
|
|
75
92
|
}
|
|
76
93
|
} catch (error) {
|
|
77
|
-
|
|
78
|
-
if (summary.errors.length < 5) summary.errors.push({
|
|
79
|
-
repository: repository.fullName,
|
|
80
|
-
issueNumber,
|
|
81
|
-
error: error instanceof Error ? error.message : String(error)
|
|
82
|
-
});
|
|
94
|
+
recordFailure(repository, error, issueNumber);
|
|
83
95
|
}
|
|
84
96
|
} catch (error) {
|
|
85
|
-
|
|
86
|
-
if (summary.errors.length < 5) summary.errors.push({
|
|
87
|
-
repository: repository.fullName,
|
|
88
|
-
error: error instanceof Error ? error.message : String(error)
|
|
89
|
-
});
|
|
97
|
+
recordFailure(repository, error);
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
return summary;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/github/issue-reconciler.ts"],"sourcesContent":["import type { IntegrationContext } from '../base.js';\nimport {\n githubRulesOptions,\n reconcilableIssueNumber,\n reconciledIssueClosedEvent,\n RECONCILE_ERROR_SAMPLE_LIMIT,\n sameStrings
|
|
1
|
+
{"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/github/issue-reconciler.ts"],"sourcesContent":["import type { IntegrationContext } from '../base.js';\nimport {\n githubRulesOptions,\n reconcilableIssueNumber,\n reconciledIssueClosedEvent,\n RECONCILE_ERROR_SAMPLE_LIMIT,\n sameStrings,\n sweepTrustLookup,\n GithubRules,\n} from './rules.js';\nimport type { GithubIssueFetcher, GithubRulesIntegration, GithubRulesOptions, ReconcileRepository } from './rules.js';\n\nexport interface GithubIssueReconcileSummary {\n /** Repositories included in the sweep. */\n repositories: number;\n /** Issue cards whose live state was fetched. */\n checked: number;\n /** Open issues with metadata patches. */\n updated: number;\n /** Closed issues replayed through the rules ingress. */\n closed: number;\n /** Errors encountered during the sweep. */\n failed: number;\n /** Error samples with context. */\n errors: Array<{ repository: string; issueNumber?: number; error: string }>;\n}\n\nexport type GithubIssueReconciler = (repositories: ReconcileRepository[]) => Promise<GithubIssueReconcileSummary>;\n\nexport function createGithubIssueReconciler(\n options: GithubRulesOptions,\n fetchIssue: GithubIssueFetcher,\n): GithubIssueReconciler {\n const rules = new GithubRules(options);\n return async repositories => {\n const summary: GithubIssueReconcileSummary = {\n repositories: 0,\n checked: 0,\n updated: 0,\n closed: 0,\n failed: 0,\n errors: [],\n };\n const recordFailure = (repository: ReconcileRepository, error: unknown, issueNumber?: number) => {\n summary.failed += 1;\n if (summary.errors.length < RECONCILE_ERROR_SAMPLE_LIMIT) {\n summary.errors.push({\n repository: repository.fullName,\n ...(issueNumber === undefined ? {} : { issueNumber }),\n error: error instanceof Error ? error.message : String(error),\n });\n }\n };\n\n for (const repository of repositories) {\n summary.repositories += 1;\n const authorTrust = sweepTrustLookup(options.github, repository);\n\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\n // Collect issue cards: number -> items (skip terminal stages)\n const itemsByNumber = new Map<number, import('../../storage/domains/work-items/base.js').WorkItemRow[]>();\n\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 issueNumber = reconcilableIssueNumber(item, repository);\n if (!issueNumber) continue;\n const stage = item.stages[0];\n if (stage === 'done' || stage === 'canceled') continue; // terminal, skip\n let list = itemsByNumber.get(issueNumber);\n if (!list) {\n list = [];\n itemsByNumber.set(issueNumber, list);\n }\n list.push(item);\n }\n }\n\n // Fetch and reconcile each unique issue\n for (const [issueNumber, items] of itemsByNumber) {\n try {\n const state = await fetchIssue({\n installationId: repository.installationId,\n repository: repository.fullName,\n number: issueNumber,\n });\n if (!state) continue; // missing or PR-backed\n summary.checked += 1;\n\n // Close detection: replay through rules ingress\n if (state.state === 'closed') {\n await rules.ingest(reconciledIssueClosedEvent(repository, issueNumber, state));\n summary.closed += 1;\n continue; // card transitions, skip metadata patch\n }\n\n // Metadata sync for open issues\n const desiredMetadata = {\n githubRepositoryId: repository.id,\n githubIssueNumber: issueNumber,\n state: 'open' as const,\n ...(state.author === undefined ? {} : { author: state.author }),\n ...(state.assignees === undefined ? {} : { assignees: state.assignees }),\n ...(state.labels === undefined ? {} : { labels: state.labels }),\n };\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, issueNumber);\n }\n }\n\n for (const item of items) {\n const current = item.metadata ?? {};\n const trustStale = authorTrusted !== undefined && current.authorTrusted !== authorTrusted;\n const metadataChanged =\n trustStale ||\n current.githubRepositoryId !== desiredMetadata.githubRepositoryId ||\n current.githubIssueNumber !== desiredMetadata.githubIssueNumber ||\n current.state !== desiredMetadata.state ||\n (state.author !== undefined && current.author !== state.author) ||\n (state.assignees !== undefined && !sameStrings(current.assignees, state.assignees)) ||\n (state.labels !== undefined && !sameStrings(current.labels, state.labels));\n\n if (!metadataChanged) continue;\n\n await options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { ...current, ...desiredMetadata, ...(trustStale ? { authorTrusted } : {}) } },\n });\n summary.updated += 1;\n }\n } catch (error) {\n recordFailure(repository, error, issueNumber);\n }\n }\n } catch (error) {\n recordFailure(repository, error);\n }\n }\n\n return summary;\n };\n}\n\nexport function attachGithubIssueReconciler(\n github: GithubRulesIntegration,\n context: IntegrationContext,\n fetchIssue: GithubIssueFetcher,\n): GithubIssueReconciler | undefined {\n if (!context.rules) return undefined;\n const options = githubRulesOptions(github, context);\n if (!options) return undefined;\n return createGithubIssueReconciler(options, fetchIssue);\n}"],"mappings":"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"}
|
|
@@ -7,6 +7,12 @@ import type { IntegrationContext } from '../base.js';
|
|
|
7
7
|
import type { GithubAppIdentity } from './app-identity.js';
|
|
8
8
|
import type { GithubRepositoryPermission } from './integration.js';
|
|
9
9
|
import type { ParsedGithubWebhook } from './webhook.js';
|
|
10
|
+
export declare function trustedCollaborator(github: GithubRulesIntegration, input: {
|
|
11
|
+
installationId: number;
|
|
12
|
+
repository: string;
|
|
13
|
+
login: string;
|
|
14
|
+
}): Promise<boolean>;
|
|
15
|
+
export declare function sweepTrustLookup(github: GithubRulesIntegration, repository: ReconcileRepository): (login: string) => Promise<boolean>;
|
|
10
16
|
export interface GithubRulesIntegration {
|
|
11
17
|
readonly slug?: string;
|
|
12
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAKV,YAAY,EACb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAC3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,8CAA8C,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -95,14 +95,33 @@ function workItemSource(item) {
|
|
|
95
95
|
function workItemSourceKey(item) {
|
|
96
96
|
return item.externalSource?.externalId ?? null;
|
|
97
97
|
}
|
|
98
|
+
async function trustedCollaborator(github, input) {
|
|
99
|
+
const permission = await github.getRepositoryCollaboratorPermission(input.installationId, input.repository, input.login);
|
|
100
|
+
return permission !== void 0 && TRUSTED_PERMISSIONS.has(permission);
|
|
101
|
+
}
|
|
102
|
+
function authorAwaitingTrust(item, repository) {
|
|
103
|
+
const metadata = item.metadata ?? {};
|
|
104
|
+
if (typeof metadata.author !== "string" || metadata.authorTrusted !== void 0) return void 0;
|
|
105
|
+
if ((reconcilablePullRequestNumber(item, repository) ?? reconcilableIssueNumber(item, repository)) === void 0) return void 0;
|
|
106
|
+
if (!item.externalSource?.url && /^github-(?:pr|issue):\d+$/.test(item.externalSource?.externalId ?? "") && metadata.githubRepositoryId !== repository.id) return void 0;
|
|
107
|
+
return metadata.author;
|
|
108
|
+
}
|
|
109
|
+
function sweepTrustLookup(github, repository) {
|
|
110
|
+
const cache = /* @__PURE__ */ new Map();
|
|
111
|
+
return async (login) => {
|
|
112
|
+
const cached = cache.get(login);
|
|
113
|
+
if (cached !== void 0) return cached;
|
|
114
|
+
const trusted = await trustedCollaborator(github, {
|
|
115
|
+
installationId: repository.installationId,
|
|
116
|
+
repository: repository.fullName,
|
|
117
|
+
login
|
|
118
|
+
});
|
|
119
|
+
cache.set(login, trusted);
|
|
120
|
+
return trusted;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
98
123
|
async function githubActor(github, input) {
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const permission = await github.getRepositoryCollaboratorPermission(input.installationId, input.repository, input.login);
|
|
102
|
-
trusted = permission !== void 0 && TRUSTED_PERMISSIONS.has(permission);
|
|
103
|
-
} catch {
|
|
104
|
-
trusted = false;
|
|
105
|
-
}
|
|
124
|
+
const trusted = await trustedCollaborator(github, input).catch(() => false);
|
|
106
125
|
return {
|
|
107
126
|
type: "github",
|
|
108
127
|
login: input.login,
|
|
@@ -444,7 +463,7 @@ function reconciledClosedEvent(repository, pullRequestNumber, state) {
|
|
|
444
463
|
}
|
|
445
464
|
};
|
|
446
465
|
}
|
|
447
|
-
function reconciledPullRequestMetadata(state, reconciliation) {
|
|
466
|
+
function reconciledPullRequestMetadata(state, reconciliation, authorTrusted) {
|
|
448
467
|
return {
|
|
449
468
|
state: state.state,
|
|
450
469
|
draft: state.draft,
|
|
@@ -453,6 +472,7 @@ function reconciledPullRequestMetadata(state, reconciliation) {
|
|
|
453
472
|
...state.assignees ? { assignees: state.assignees } : {},
|
|
454
473
|
...state.requestedReviewers ? { requestedReviewers: state.requestedReviewers } : {},
|
|
455
474
|
...state.labels ? { labels: state.labels } : {},
|
|
475
|
+
...authorTrusted === void 0 ? {} : { authorTrusted },
|
|
456
476
|
[FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: reconciliation === "settled" ? state.merged ? "merged" : "closed" : null
|
|
457
477
|
};
|
|
458
478
|
}
|
|
@@ -504,6 +524,7 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
504
524
|
summary.repositories = scoped.length;
|
|
505
525
|
for (const repository of scoped) {
|
|
506
526
|
let cardsByNumber;
|
|
527
|
+
let unanswered;
|
|
507
528
|
try {
|
|
508
529
|
const projects = await options.sourceControl.projectRepositories.listByExternalRepository({
|
|
509
530
|
installationExternalId: String(repository.installationId),
|
|
@@ -511,6 +532,7 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
511
532
|
});
|
|
512
533
|
if (projects.length === 0) continue;
|
|
513
534
|
cardsByNumber = /* @__PURE__ */ new Map();
|
|
535
|
+
unanswered = [];
|
|
514
536
|
for (const project of projects) {
|
|
515
537
|
const items = await options.storage.list({
|
|
516
538
|
orgId: project.orgId,
|
|
@@ -518,6 +540,11 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
518
540
|
});
|
|
519
541
|
for (const item of items) {
|
|
520
542
|
const stage = item.stages[0];
|
|
543
|
+
const unansweredAuthor = authorAwaitingTrust(item, repository);
|
|
544
|
+
if (unansweredAuthor) unanswered.push({
|
|
545
|
+
item,
|
|
546
|
+
author: unansweredAuthor
|
|
547
|
+
});
|
|
521
548
|
const pullRequestNumber = reconcilablePullRequestNumber(item, repository);
|
|
522
549
|
if (!pullRequestNumber) continue;
|
|
523
550
|
const metadata = item.metadata ?? {};
|
|
@@ -533,6 +560,17 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
533
560
|
recordFailure(repository, error);
|
|
534
561
|
continue;
|
|
535
562
|
}
|
|
563
|
+
const authorTrust = sweepTrustLookup(options.github, repository);
|
|
564
|
+
for (const { item, author } of unanswered) try {
|
|
565
|
+
await options.storage.update({
|
|
566
|
+
orgId: item.orgId,
|
|
567
|
+
id: item.id,
|
|
568
|
+
userId: "factory-rule-dispatcher",
|
|
569
|
+
patch: { metadata: { authorTrusted: await authorTrust(author) } }
|
|
570
|
+
});
|
|
571
|
+
} catch (error) {
|
|
572
|
+
recordFailure(repository, error);
|
|
573
|
+
}
|
|
536
574
|
for (const [pullRequestNumber, cards] of cardsByNumber) try {
|
|
537
575
|
const state = await fetchPullRequest({
|
|
538
576
|
installationId: repository.installationId,
|
|
@@ -541,6 +579,12 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
541
579
|
});
|
|
542
580
|
summary.checked += 1;
|
|
543
581
|
if (!state) continue;
|
|
582
|
+
let authorTrusted;
|
|
583
|
+
if (state.author !== void 0) try {
|
|
584
|
+
authorTrusted = await authorTrust(state.author);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
recordFailure(repository, error, pullRequestNumber);
|
|
587
|
+
}
|
|
544
588
|
for (const card of cards) {
|
|
545
589
|
if (state.state === "closed") continue;
|
|
546
590
|
const metadata = card.metadata ?? {};
|
|
@@ -549,7 +593,8 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
549
593
|
const assigneesChanged = !sameStrings(metadata.assignees, state.assignees);
|
|
550
594
|
const reviewersChanged = !sameStrings(metadata.requestedReviewers, state.requestedReviewers);
|
|
551
595
|
const labelsChanged = !sameStrings(metadata.labels, state.labels);
|
|
552
|
-
const
|
|
596
|
+
const trustStale = authorTrusted !== void 0 && metadata.authorTrusted !== authorTrusted;
|
|
597
|
+
const metadataChanged = statusChanged || authorChanged || assigneesChanged || reviewersChanged || labelsChanged || trustStale;
|
|
553
598
|
const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];
|
|
554
599
|
if (!metadataChanged && reconciliation !== "merged" && reconciliation !== "closed") continue;
|
|
555
600
|
try {
|
|
@@ -557,7 +602,7 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
557
602
|
orgId: card.orgId,
|
|
558
603
|
id: card.id,
|
|
559
604
|
userId: "factory-rule-dispatcher",
|
|
560
|
-
patch: { metadata: reconciledPullRequestMetadata(state, "clear") }
|
|
605
|
+
patch: { metadata: reconciledPullRequestMetadata(state, "clear", trustStale ? authorTrusted : void 0) }
|
|
561
606
|
});
|
|
562
607
|
} catch (error) {
|
|
563
608
|
recordFailure(repository, error, pullRequestNumber);
|
|
@@ -583,12 +628,13 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
583
628
|
await retireReconciledSubscriptions(options.integrationStorage, repository, pullRequestNumber, state.merged);
|
|
584
629
|
for (const card of cards) {
|
|
585
630
|
if (cleanupFailures.has(card.id)) continue;
|
|
631
|
+
const trustStale = authorTrusted !== void 0 && (card.metadata ?? {}).authorTrusted !== authorTrusted;
|
|
586
632
|
try {
|
|
587
633
|
await options.storage.update({
|
|
588
634
|
orgId: card.orgId,
|
|
589
635
|
id: card.id,
|
|
590
636
|
userId: "factory-rule-dispatcher",
|
|
591
|
-
patch: { metadata: reconciledPullRequestMetadata(state, "settled") }
|
|
637
|
+
patch: { metadata: reconciledPullRequestMetadata(state, "settled", trustStale ? authorTrusted : void 0) }
|
|
592
638
|
});
|
|
593
639
|
} catch (error) {
|
|
594
640
|
recordFailure(repository, error, pullRequestNumber);
|
|
@@ -626,6 +672,6 @@ function attachGithubReconciler(github, context, fetchPullRequest) {
|
|
|
626
672
|
return createGithubPullRequestReconciler(options, fetchPullRequest);
|
|
627
673
|
}
|
|
628
674
|
//#endregion
|
|
629
|
-
export { GithubRules, RECONCILE_ERROR_SAMPLE_LIMIT, attachGithubReconciler, attachGithubRules, createGithubPullRequestReconciler, githubRulesOptions, reconcilableIssueNumber, reconciledClosedEvent, reconciledIssueClosedEvent, sameStrings };
|
|
675
|
+
export { GithubRules, RECONCILE_ERROR_SAMPLE_LIMIT, attachGithubReconciler, attachGithubRules, createGithubPullRequestReconciler, githubRulesOptions, reconcilableIssueNumber, reconciledClosedEvent, reconciledIssueClosedEvent, sameStrings, sweepTrustLookup, trustedCollaborator };
|
|
630
676
|
|
|
631
677
|
//# sourceMappingURL=rules.js.map
|