@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
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;AAQrD,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,CAoHvB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,kBAAkB,EAC3B,UAAU,EAAE,kBAAkB,GAC7B,qBAAqB,GAAG,SAAS,CAKnC"}
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
- if (!(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;
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
- summary.failed += 1;
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
- summary.failed += 1;
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,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\n for (const repository of repositories) {\n summary.repositories += 1;\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\n for (const item of items) {\n const current = item.metadata ?? {};\n const metadataChanged =\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 } },\n });\n summary.updated += 1;\n }\n } catch (error) {\n summary.failed += 1;\n if (summary.errors.length < RECONCILE_ERROR_SAMPLE_LIMIT) {\n summary.errors.push({\n repository: repository.fullName,\n issueNumber,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n } catch (error) {\n summary.failed += 1;\n if (summary.errors.length < RECONCILE_ERROR_SAMPLE_LIMIT) {\n summary.errors.push({\n repository: repository.fullName,\n error: error instanceof Error ? error.message : String(error),\n });\n }\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":";;AA2BA,SAAgB,4BACd,SACA,YACuB;CACvB,MAAM,QAAQ,IAAI,YAAY,OAAO;CACrC,OAAO,OAAM,iBAAgB;EAC3B,MAAM,UAAuC;GAC3C,cAAc;GACd,SAAS;GACT,SAAS;GACT,QAAQ;GACR,QAAQ;GACR,QAAQ,CAAC;EACX;EAEA,KAAK,MAAM,cAAc,cAAc;GACrC,QAAQ,gBAAgB;GAExB,IAAI;IACF,MAAM,WAAW,MAAM,QAAQ,cAAc,oBAAoB,yBAAyB;KACxF,wBAAwB,OAAO,WAAW,cAAc;KACxD,sBAAsB,OAAO,WAAW,EAAE;IAC5C,CAAC;IACD,IAAI,SAAS,WAAW,GAAG;IAG3B,MAAM,gCAAgB,IAAI,IAA8E;IAExG,KAAK,MAAM,WAAW,UAAU;KAC9B,MAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK;MACvC,OAAO,QAAQ;MACf,kBAAkB,QAAQ;KAC5B,CAAC;KACD,KAAK,MAAM,QAAQ,OAAO;MACxB,MAAM,cAAc,wBAAwB,MAAM,UAAU;MAC5D,IAAI,CAAC,aAAa;MAClB,MAAM,QAAQ,KAAK,OAAO;MAC1B,IAAI,UAAU,UAAU,UAAU,YAAY;MAC9C,IAAI,OAAO,cAAc,IAAI,WAAW;MACxC,IAAI,CAAC,MAAM;OACT,OAAO,CAAC;OACR,cAAc,IAAI,aAAa,IAAI;MACrC;MACA,KAAK,KAAK,IAAI;KAChB;IACF;IAGA,KAAK,MAAM,CAAC,aAAa,UAAU,eACjC,IAAI;KACF,MAAM,QAAQ,MAAM,WAAW;MAC7B,gBAAgB,WAAW;MAC3B,YAAY,WAAW;MACvB,QAAQ;KACV,CAAC;KACD,IAAI,CAAC,OAAO;KACZ,QAAQ,WAAW;KAGnB,IAAI,MAAM,UAAU,UAAU;MAC5B,MAAM,MAAM,OAAO,2BAA2B,YAAY,aAAa,KAAK,CAAC;MAC7E,QAAQ,UAAU;MAClB;KACF;KAGA,MAAM,kBAAkB;MACtB,oBAAoB,WAAW;MAC/B,mBAAmB;MACnB,OAAO;MACP,GAAI,MAAM,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;MAC7D,GAAI,MAAM,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,MAAM,UAAU;MACtE,GAAI,MAAM,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;KAC/D;KAEA,KAAK,MAAM,QAAQ,OAAO;MACxB,MAAM,UAAU,KAAK,YAAY,CAAC;MASlC,IAAI,EAPF,QAAQ,uBAAuB,gBAAgB,sBAC/C,QAAQ,sBAAsB,gBAAgB,qBAC9C,QAAQ,UAAU,gBAAgB,SACjC,MAAM,WAAW,KAAA,KAAa,QAAQ,WAAW,MAAM,UACvD,MAAM,cAAc,KAAA,KAAa,CAAC,YAAY,QAAQ,WAAW,MAAM,SAAS,KAChF,MAAM,WAAW,KAAA,KAAa,CAAC,YAAY,QAAQ,QAAQ,MAAM,MAAM,IAEpD;MAEtB,MAAM,QAAQ,QAAQ,OAAO;OAC3B,OAAO,KAAK;OACZ,IAAI,KAAK;OACT,QAAQ;OACR,OAAO,EAAE,UAAU;QAAE,GAAG;QAAS,GAAG;OAAgB,EAAE;MACxD,CAAC;MACD,QAAQ,WAAW;KACrB;IACF,SAAS,OAAO;KACd,QAAQ,UAAU;KAClB,IAAI,QAAQ,OAAO,SAAA,GACjB,QAAQ,OAAO,KAAK;MAClB,YAAY,WAAW;MACvB;MACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KAC9D,CAAC;IAEL;GAEJ,SAAS,OAAO;IACd,QAAQ,UAAU;IAClB,IAAI,QAAQ,OAAO,SAAA,GACjB,QAAQ,OAAO,KAAK;KAClB,YAAY,WAAW;KACvB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC9D,CAAC;GAEL;EACF;EAEA,OAAO;CACT;AACF;AAEA,SAAgB,4BACd,QACA,SACA,YACmC;CACnC,IAAI,CAAC,QAAQ,OAAO,OAAO,KAAA;CAC3B,MAAM,UAAU,mBAAmB,QAAQ,OAAO;CAClD,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,OAAO,4BAA4B,SAAS,UAAU;AACxD"}
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":"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"}
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
- let trusted = false;
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 metadataChanged = statusChanged || authorChanged || assigneesChanged || reviewersChanged || labelsChanged;
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