@mastra/factory 0.16.0-alpha.10 → 0.16.0-alpha.12

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 (216) hide show
  1. package/README.md +37 -0
  2. package/dist/auth.js +1 -1
  3. package/dist/auth.js.map +1 -1
  4. package/dist/boards/review.d.ts.map +1 -1
  5. package/dist/boards/review.js +19 -8
  6. package/dist/boards/review.js.map +1 -1
  7. package/dist/boards/work.d.ts.map +1 -1
  8. package/dist/boards/work.js +31 -3
  9. package/dist/boards/work.js.map +1 -1
  10. package/dist/capabilities/intake.d.ts +4 -0
  11. package/dist/capabilities/intake.d.ts.map +1 -1
  12. package/dist/capabilities/version-control.d.ts +15 -0
  13. package/dist/capabilities/version-control.d.ts.map +1 -1
  14. package/dist/factory.d.ts +7 -7
  15. package/dist/factory.d.ts.map +1 -1
  16. package/dist/factory.js +100 -40
  17. package/dist/factory.js.map +1 -1
  18. package/dist/integrations/base.d.ts +3 -0
  19. package/dist/integrations/base.d.ts.map +1 -1
  20. package/dist/integrations/github/integration.d.ts.map +1 -1
  21. package/dist/integrations/github/integration.js +21 -0
  22. package/dist/integrations/github/integration.js.map +1 -1
  23. package/dist/integrations/github/routes.js +2 -1
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/sandbox.d.ts +41 -42
  26. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  27. package/dist/integrations/github/sandbox.js +416 -227
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/webhook.d.ts +2 -5
  30. package/dist/integrations/github/webhook.d.ts.map +1 -1
  31. package/dist/integrations/github/webhook.js +8 -48
  32. package/dist/integrations/github/webhook.js.map +1 -1
  33. package/dist/integrations/gitlab/agent-tools.d.ts +14 -0
  34. package/dist/integrations/gitlab/agent-tools.d.ts.map +1 -0
  35. package/dist/integrations/gitlab/agent-tools.js +42 -0
  36. package/dist/integrations/gitlab/agent-tools.js.map +1 -0
  37. package/dist/integrations/gitlab/api.d.ts +222 -0
  38. package/dist/integrations/gitlab/api.d.ts.map +1 -0
  39. package/dist/integrations/gitlab/api.js +274 -0
  40. package/dist/integrations/gitlab/api.js.map +1 -0
  41. package/dist/integrations/gitlab/default-rules.d.ts +125 -0
  42. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -0
  43. package/dist/integrations/gitlab/default-rules.js +172 -0
  44. package/dist/integrations/gitlab/default-rules.js.map +1 -0
  45. package/dist/integrations/gitlab/integration.d.ts +138 -0
  46. package/dist/integrations/gitlab/integration.d.ts.map +1 -0
  47. package/dist/integrations/gitlab/integration.js +705 -0
  48. package/dist/integrations/gitlab/integration.js.map +1 -0
  49. package/dist/integrations/gitlab/issue-reconciler.d.ts +6 -0
  50. package/dist/integrations/gitlab/issue-reconciler.d.ts.map +1 -0
  51. package/dist/integrations/gitlab/issue-reconciler.js +123 -0
  52. package/dist/integrations/gitlab/issue-reconciler.js.map +1 -0
  53. package/dist/integrations/gitlab/merge-request-reconciler.d.ts +6 -0
  54. package/dist/integrations/gitlab/merge-request-reconciler.d.ts.map +1 -0
  55. package/dist/integrations/gitlab/merge-request-reconciler.js +267 -0
  56. package/dist/integrations/gitlab/merge-request-reconciler.js.map +1 -0
  57. package/dist/integrations/gitlab/reconciler.d.ts +5 -0
  58. package/dist/integrations/gitlab/reconciler.d.ts.map +1 -0
  59. package/dist/integrations/gitlab/reconciler.js +31 -0
  60. package/dist/integrations/gitlab/reconciler.js.map +1 -0
  61. package/dist/integrations/gitlab/reconciliation-config.d.ts +3 -0
  62. package/dist/integrations/gitlab/reconciliation-config.d.ts.map +1 -0
  63. package/dist/integrations/gitlab/reconciliation-config.js +17 -0
  64. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -0
  65. package/dist/integrations/gitlab/routes.d.ts +20 -0
  66. package/dist/integrations/gitlab/routes.d.ts.map +1 -0
  67. package/dist/integrations/gitlab/routes.js +473 -0
  68. package/dist/integrations/gitlab/routes.js.map +1 -0
  69. package/dist/integrations/gitlab/rules.d.ts +35 -0
  70. package/dist/integrations/gitlab/rules.d.ts.map +1 -0
  71. package/dist/integrations/gitlab/rules.js +391 -0
  72. package/dist/integrations/gitlab/rules.js.map +1 -0
  73. package/dist/integrations/gitlab/session-subscriptions.d.ts +32 -0
  74. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -0
  75. package/dist/integrations/gitlab/session-subscriptions.js +192 -0
  76. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -0
  77. package/dist/integrations/gitlab/subscriptions.d.ts +70 -0
  78. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -0
  79. package/dist/integrations/gitlab/subscriptions.js +76 -0
  80. package/dist/integrations/gitlab/subscriptions.js.map +1 -0
  81. package/dist/integrations/gitlab/version-control.d.ts +16 -0
  82. package/dist/integrations/gitlab/version-control.d.ts.map +1 -0
  83. package/dist/integrations/gitlab/version-control.js +622 -0
  84. package/dist/integrations/gitlab/version-control.js.map +1 -0
  85. package/dist/integrations/gitlab/webhook-dispatch.d.ts +75 -0
  86. package/dist/integrations/gitlab/webhook-dispatch.d.ts.map +1 -0
  87. package/dist/integrations/gitlab/webhook-dispatch.js +235 -0
  88. package/dist/integrations/gitlab/webhook-dispatch.js.map +1 -0
  89. package/dist/integrations/gitlab/webhook.d.ts +72 -0
  90. package/dist/integrations/gitlab/webhook.d.ts.map +1 -0
  91. package/dist/integrations/gitlab/webhook.js +201 -0
  92. package/dist/integrations/gitlab/webhook.js.map +1 -0
  93. package/dist/integrations/incidentio/integration.js +1 -1
  94. package/dist/integrations/issue-reconciler.d.ts +3 -3
  95. package/dist/integrations/issue-reconciler.d.ts.map +1 -1
  96. package/dist/integrations/issue-reconciler.js +22 -2
  97. package/dist/integrations/issue-reconciler.js.map +1 -1
  98. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  99. package/dist/integrations/platform/github/integration.js +21 -0
  100. package/dist/integrations/platform/github/integration.js.map +1 -1
  101. package/dist/integrations/platform/gitlab/event-worker.d.ts +64 -0
  102. package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -0
  103. package/dist/integrations/platform/gitlab/event-worker.js +306 -0
  104. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -0
  105. package/dist/integrations/platform/gitlab/integration.d.ts +57 -0
  106. package/dist/integrations/platform/gitlab/integration.d.ts.map +1 -0
  107. package/dist/integrations/platform/gitlab/integration.js +125 -0
  108. package/dist/integrations/platform/gitlab/integration.js.map +1 -0
  109. package/dist/integrations/platform/incidentio/integration.js +1 -1
  110. package/dist/integrations/slack/integration.d.ts.map +1 -1
  111. package/dist/integrations/slack/integration.js +3 -3
  112. package/dist/integrations/slack/integration.js.map +1 -1
  113. package/dist/integrations/slack/slack.d.ts +2 -0
  114. package/dist/integrations/slack/slack.d.ts.map +1 -1
  115. package/dist/integrations/slack/slack.js +21 -7
  116. package/dist/integrations/slack/slack.js.map +1 -1
  117. package/dist/integrations/subscription-session.d.ts +37 -0
  118. package/dist/integrations/subscription-session.d.ts.map +1 -0
  119. package/dist/integrations/subscription-session.js +83 -0
  120. package/dist/integrations/subscription-session.js.map +1 -0
  121. package/dist/packages/_internals/workspace/dist/index.js +1 -11
  122. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  123. package/dist/routes/intake.d.ts.map +1 -1
  124. package/dist/routes/intake.js +4 -2
  125. package/dist/routes/intake.js.map +1 -1
  126. package/dist/routes/oauth.js +1 -1
  127. package/dist/routes/projects.d.ts +1 -0
  128. package/dist/routes/projects.d.ts.map +1 -1
  129. package/dist/routes/projects.js +1 -0
  130. package/dist/routes/projects.js.map +1 -1
  131. package/dist/routes/source-control-sessions.d.ts +20 -0
  132. package/dist/routes/source-control-sessions.d.ts.map +1 -0
  133. package/dist/routes/source-control-sessions.js +319 -0
  134. package/dist/routes/source-control-sessions.js.map +1 -0
  135. package/dist/routes/source-control-settings.d.ts +10 -0
  136. package/dist/routes/source-control-settings.d.ts.map +1 -0
  137. package/dist/routes/source-control-settings.js +87 -0
  138. package/dist/routes/source-control-settings.js.map +1 -0
  139. package/dist/routes/surface.d.ts +3 -3
  140. package/dist/routes/surface.d.ts.map +1 -1
  141. package/dist/routes/surface.js +55 -13
  142. package/dist/routes/surface.js.map +1 -1
  143. package/dist/routes/tenant-credentials.d.ts +8 -0
  144. package/dist/routes/tenant-credentials.d.ts.map +1 -1
  145. package/dist/routes/tenant-credentials.js +20 -3
  146. package/dist/routes/tenant-credentials.js.map +1 -1
  147. package/dist/routes/work-items.d.ts.map +1 -1
  148. package/dist/routes/work-items.js +1 -1
  149. package/dist/routes/work-items.js.map +1 -1
  150. package/dist/rules/dispatcher.d.ts.map +1 -1
  151. package/dist/rules/dispatcher.js +7 -1
  152. package/dist/rules/dispatcher.js.map +1 -1
  153. package/dist/rules/start-coordinator.d.ts +1 -1
  154. package/dist/rules/start-coordinator.d.ts.map +1 -1
  155. package/dist/rules/start-coordinator.js +3 -2
  156. package/dist/rules/start-coordinator.js.map +1 -1
  157. package/dist/rules/transition-service.d.ts.map +1 -1
  158. package/dist/rules/transition-service.js +6 -3
  159. package/dist/rules/transition-service.js.map +1 -1
  160. package/dist/rules/types.d.ts +67 -3
  161. package/dist/rules/types.d.ts.map +1 -1
  162. package/dist/rules/types.js +23 -2
  163. package/dist/rules/types.js.map +1 -1
  164. package/dist/rules/validation.d.ts.map +1 -1
  165. package/dist/rules/validation.js +2 -0
  166. package/dist/rules/validation.js.map +1 -1
  167. package/dist/sandbox/git-ref.d.ts +7 -0
  168. package/dist/sandbox/git-ref.d.ts.map +1 -0
  169. package/dist/sandbox/git-ref.js +15 -0
  170. package/dist/sandbox/git-ref.js.map +1 -0
  171. package/dist/sandbox/session-retirement.js +1 -1
  172. package/dist/sandbox/workdir.d.ts.map +1 -1
  173. package/dist/sandbox/workdir.js +6 -6
  174. package/dist/sandbox/workdir.js.map +1 -1
  175. package/dist/session/factory-session.d.ts +31 -1
  176. package/dist/session/factory-session.d.ts.map +1 -1
  177. package/dist/session/factory-session.js +60 -1
  178. package/dist/session/factory-session.js.map +1 -1
  179. package/dist/session/source-control-tools.d.ts +16 -0
  180. package/dist/session/source-control-tools.d.ts.map +1 -0
  181. package/dist/session/source-control-tools.js +464 -0
  182. package/dist/session/source-control-tools.js.map +1 -0
  183. package/dist/storage/domains/intake/base.d.ts +18 -0
  184. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  185. package/dist/storage/domains/intake/base.js +77 -0
  186. package/dist/storage/domains/intake/base.js.map +1 -1
  187. package/dist/work-item-branch.d.ts +3 -1
  188. package/dist/work-item-branch.d.ts.map +1 -1
  189. package/dist/work-item-branch.js +22 -4
  190. package/dist/work-item-branch.js.map +1 -1
  191. package/dist/workspace.d.ts +15 -1
  192. package/dist/workspace.d.ts.map +1 -1
  193. package/dist/workspace.js +57 -23
  194. package/dist/workspace.js.map +1 -1
  195. package/factory-skills/factory-gitlab-rereview/SKILL.md +19 -0
  196. package/factory-skills/factory-gitlab-review/SKILL.md +30 -0
  197. package/factory-skills/factory-rereview/SKILL.md +13 -6
  198. package/factory-skills/factory-review/SKILL.md +13 -6
  199. package/factory-skills/factory-review/references/archaeology.md +225 -0
  200. package/factory-skills/factory-review/references/categories/README.md +34 -0
  201. package/factory-skills/factory-review/references/categories/behavior-change.md +36 -0
  202. package/factory-skills/factory-review/references/categories/bug-fix.md +37 -0
  203. package/factory-skills/factory-review/references/categories/docs.md +32 -0
  204. package/factory-skills/factory-review/references/categories/experimental-flagged.md +34 -0
  205. package/factory-skills/factory-review/references/categories/infra-tooling.md +37 -0
  206. package/factory-skills/factory-review/references/categories/internal-capability.md +35 -0
  207. package/factory-skills/factory-review/references/categories/large-cross-cutting.md +35 -0
  208. package/factory-skills/factory-review/references/categories/mechanical.md +36 -0
  209. package/factory-skills/factory-review/references/categories/performance.md +33 -0
  210. package/factory-skills/factory-review/references/categories/public-api.md +38 -0
  211. package/factory-skills/factory-review/references/categories/refactor.md +35 -0
  212. package/factory-skills/factory-review/references/categories/revert.md +31 -0
  213. package/factory-skills/factory-review/references/categories/schema-storage.md +39 -0
  214. package/factory-skills/factory-review/references/categories/security.md +35 -0
  215. package/factory-skills/factory-review/references/categories/tests-only.md +38 -0
  216. package/package.json +9 -9
@@ -0,0 +1,201 @@
1
+ import { dispatchGitLabWebhook } from "./webhook-dispatch.js";
2
+ import { createHash, timingSafeEqual } from "crypto";
3
+ //#region src/integrations/gitlab/webhook.ts
4
+ const SUPPORTED_GITLAB_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
5
+ "Issue Hook",
6
+ "Note Hook",
7
+ "Merge Request Hook",
8
+ "Push Hook"
9
+ ]);
10
+ /**
11
+ * `object_kind` values GitLab puts in every webhook body, mapped to the
12
+ * `X-Gitlab-Event` header the direct route keys on. A body reaching Factory
13
+ * without its headers (the Platform event log stores only the payload) is
14
+ * normalized through this table so both ingress paths see the same event name.
15
+ */
16
+ const GITLAB_OBJECT_KIND_EVENTS = {
17
+ issue: "Issue Hook",
18
+ note: "Note Hook",
19
+ merge_request: "Merge Request Hook",
20
+ push: "Push Hook"
21
+ };
22
+ function normalizeHeader(value) {
23
+ if (!value) return null;
24
+ const trimmed = value.trim();
25
+ return trimmed.length > 0 ? trimmed : null;
26
+ }
27
+ function normalizeInstanceHost(value) {
28
+ if (!value) return void 0;
29
+ try {
30
+ return new URL(value.includes("://") ? value : "https://" + value).host.toLowerCase();
31
+ } catch {
32
+ return;
33
+ }
34
+ }
35
+ function verifyGitLabToken(receivedToken, secret) {
36
+ const received = Buffer.from(receivedToken, "utf8");
37
+ const expected = Buffer.from(secret, "utf8");
38
+ return received.length === expected.length && timingSafeEqual(received, expected);
39
+ }
40
+ async function parseGitLabWebhook(c, secret) {
41
+ if (!secret) return {
42
+ status: 401,
43
+ body: {
44
+ error: "unauthorized",
45
+ message: "GitLab webhook secret is not configured"
46
+ }
47
+ };
48
+ const event = normalizeHeader(c.req.header("x-gitlab-event"));
49
+ const token = normalizeHeader(c.req.header("x-gitlab-token"));
50
+ if (!event) return {
51
+ status: 400,
52
+ body: {
53
+ error: "bad_request",
54
+ message: "Missing x-gitlab-event header"
55
+ }
56
+ };
57
+ if (!token) return {
58
+ status: 401,
59
+ body: {
60
+ error: "unauthorized",
61
+ message: "Missing x-gitlab-token header"
62
+ }
63
+ };
64
+ if (!verifyGitLabToken(token, secret)) return {
65
+ status: 401,
66
+ body: {
67
+ error: "unauthorized",
68
+ message: "Invalid GitLab webhook token"
69
+ }
70
+ };
71
+ const rawBody = await c.req.text();
72
+ let payload;
73
+ try {
74
+ payload = JSON.parse(rawBody);
75
+ } catch {
76
+ return {
77
+ status: 400,
78
+ body: {
79
+ error: "bad_request",
80
+ message: "Malformed JSON payload"
81
+ }
82
+ };
83
+ }
84
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {
85
+ status: 400,
86
+ body: {
87
+ error: "bad_request",
88
+ message: "Payload must be a JSON object"
89
+ }
90
+ };
91
+ const deliveryId = normalizeHeader(c.req.header("webhook-id")) ?? normalizeHeader(c.req.header("idempotency-key")) ?? normalizeHeader(c.req.header("x-gitlab-webhook-uuid")) ?? normalizeHeader(c.req.header("x-gitlab-event-uuid")) ?? createHash("sha256").update(event).update("\0").update(rawBody).digest("hex");
92
+ const instanceHost = normalizeInstanceHost(c.req.header("x-gitlab-instance"));
93
+ return {
94
+ event,
95
+ deliveryId,
96
+ ...instanceHost ? { instanceHost } : {},
97
+ payload
98
+ };
99
+ }
100
+ function getObject(value) {
101
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
102
+ }
103
+ function getString(value) {
104
+ return typeof value === "string" && value.length > 0 ? value : void 0;
105
+ }
106
+ function getNumber(value) {
107
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
108
+ }
109
+ function normalizeGitLabWebhookMetadata(parsed) {
110
+ const project = getObject(parsed.payload.project);
111
+ const attributes = getObject(parsed.payload.object_attributes);
112
+ const issue = getObject(parsed.payload.issue);
113
+ const mergeRequest = getObject(parsed.payload.merge_request);
114
+ const user = getObject(parsed.payload.user);
115
+ const objectIid = getNumber(attributes?.iid);
116
+ const noteableType = getString(attributes?.noteable_type);
117
+ return {
118
+ event: parsed.event,
119
+ projectId: getNumber(project?.id),
120
+ projectPath: getString(project?.path_with_namespace),
121
+ issueIid: parsed.event === "Issue Hook" ? objectIid : noteableType === "Issue" ? getNumber(issue?.iid) ?? objectIid : void 0,
122
+ mergeRequestIid: parsed.event === "Merge Request Hook" ? objectIid : noteableType === "MergeRequest" ? getNumber(mergeRequest?.iid) ?? objectIid : void 0,
123
+ noteableType,
124
+ sender: getString(parsed.payload.user_username) ?? getString(user?.username) ?? getString(user?.name)
125
+ };
126
+ }
127
+ /**
128
+ * Build the parsed form of a GitLab webhook from its raw body alone, for
129
+ * deliveries that arrive without HTTP headers. The event name comes from
130
+ * `object_kind`; the caller supplies a delivery id that is stable across
131
+ * replays so rules replay handling and session dedupe keys behave as they do
132
+ * for a direct delivery. Returns null when the body is not a webhook object or
133
+ * its kind is one Factory does not handle, which callers treat exactly like an
134
+ * unsupported event header: acknowledged and dropped.
135
+ */
136
+ function parseGitLabWebhookBody(body, deliveryId) {
137
+ const payload = getObject(body);
138
+ const objectKind = getString(payload?.object_kind);
139
+ const id = normalizeHeader(deliveryId);
140
+ if (!payload || !objectKind || !id) return null;
141
+ const event = GITLAB_OBJECT_KIND_EVENTS[objectKind];
142
+ if (!event) return null;
143
+ return {
144
+ event,
145
+ deliveryId: id,
146
+ payload
147
+ };
148
+ }
149
+ /**
150
+ * Process a parsed delivery: rules ingress first, then session dispatch when a
151
+ * controller is mounted. Ingestion failures propagate so the caller can retry
152
+ * the delivery (GitLab redelivers on a non-2xx; the poller leaves its cursor
153
+ * in place).
154
+ */
155
+ async function processGitLabWebhook(parsed, options) {
156
+ if (!SUPPORTED_GITLAB_WEBHOOK_EVENTS.has(parsed.event)) return {
157
+ status: 202,
158
+ body: {
159
+ ok: true,
160
+ ignored: true
161
+ }
162
+ };
163
+ console.info("[GitLab Webhook]", normalizeGitLabWebhookMetadata(parsed), { deliveryId: parsed.deliveryId });
164
+ if (options.ingestFactoryEvent) await options.ingestFactoryEvent(parsed);
165
+ if (!options.controller) return {
166
+ status: 202,
167
+ body: { ok: true }
168
+ };
169
+ const { ingestFactoryEvent: _ingest, controller, ...dispatch } = options;
170
+ const result = await dispatchGitLabWebhook(parsed, {
171
+ onSenderRejected: (notification) => {
172
+ console.info("[GitLab Webhook] sender not authorized", {
173
+ deliveryId: parsed.deliveryId,
174
+ repository: notification.metadata.projectPath,
175
+ sender: notification.metadata.sender,
176
+ kind: notification.kind
177
+ });
178
+ },
179
+ ...dispatch,
180
+ controller
181
+ });
182
+ if (result.failed > 0) console.warn(`[GitLab Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);
183
+ if (!result.ignored) console.info("[GitLab Webhook] session delivery", {
184
+ deliveryId: parsed.deliveryId,
185
+ ...result
186
+ });
187
+ return {
188
+ status: 202,
189
+ body: { ok: true }
190
+ };
191
+ }
192
+ async function handleGitLabWebhook(c, options) {
193
+ const parsed = await parseGitLabWebhook(c, options.webhookSecret);
194
+ if ("status" in parsed) return parsed;
195
+ const { webhookSecret: _secret, ...process } = options;
196
+ return processGitLabWebhook(parsed, process);
197
+ }
198
+ //#endregion
199
+ export { SUPPORTED_GITLAB_WEBHOOK_EVENTS, handleGitLabWebhook, normalizeGitLabWebhookMetadata, parseGitLabWebhook, parseGitLabWebhookBody, processGitLabWebhook, verifyGitLabToken };
200
+
201
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","names":[],"sources":["../../../src/integrations/gitlab/webhook.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto';\nimport type { Context } from 'hono';\n\nimport { dispatchGitLabWebhook } from './webhook-dispatch.js';\nimport type { GitLabWebhookDispatchDependencies } from './webhook-dispatch.js';\n\nexport const SUPPORTED_GITLAB_WEBHOOK_EVENTS = new Set(['Issue Hook', 'Note Hook', 'Merge Request Hook', 'Push Hook']);\n\nexport interface ParsedGitLabWebhook {\n event: string;\n deliveryId: string;\n instanceHost?: string;\n payload: Record<string, unknown>;\n}\n\nexport interface GitLabWebhookMetadata {\n event: string;\n projectId?: number;\n projectPath?: string;\n issueIid?: number;\n mergeRequestIid?: number;\n noteableType?: string;\n sender?: string;\n}\n\nexport type GitLabWebhookResult =\n | { status: 202; body: { ok: true; ignored?: true } }\n | { status: 400; body: { error: 'bad_request'; message: string } }\n | { status: 401; body: { error: 'unauthorized'; message: string } };\n\n/**\n * What processing an already-parsed delivery needs: the rules ingress and,\n * when a controller is mounted, the subscription dispatch. Shared by the\n * direct webhook route and the Platform event poller.\n */\nexport interface GitLabWebhookProcessOptions extends Partial<Omit<GitLabWebhookDispatchDependencies, 'controller'>> {\n ingestFactoryEvent?: (event: ParsedGitLabWebhook) => Promise<unknown>;\n /** When present, merge-request activity is also delivered to subscribed sessions. */\n controller?: GitLabWebhookDispatchDependencies['controller'];\n}\n\nexport interface GitLabWebhookHandlerOptions extends GitLabWebhookProcessOptions {\n webhookSecret?: string;\n}\n\n/**\n * `object_kind` values GitLab puts in every webhook body, mapped to the\n * `X-Gitlab-Event` header the direct route keys on. A body reaching Factory\n * without its headers (the Platform event log stores only the payload) is\n * normalized through this table so both ingress paths see the same event name.\n */\nconst GITLAB_OBJECT_KIND_EVENTS: Record<string, string> = {\n issue: 'Issue Hook',\n note: 'Note Hook',\n merge_request: 'Merge Request Hook',\n push: 'Push Hook',\n};\n\nfunction normalizeHeader(value: string | undefined | null): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction normalizeInstanceHost(value: string | null | undefined): string | undefined {\n if (!value) return undefined;\n try {\n return new URL(value.includes('://') ? value : 'https://' + value).host.toLowerCase();\n } catch {\n return undefined;\n }\n}\n\nexport function verifyGitLabToken(receivedToken: string, secret: string): boolean {\n const received = Buffer.from(receivedToken, 'utf8');\n const expected = Buffer.from(secret, 'utf8');\n return received.length === expected.length && timingSafeEqual(received, expected);\n}\n\nexport async function parseGitLabWebhook(\n c: Context,\n secret: string | undefined,\n): Promise<ParsedGitLabWebhook | GitLabWebhookResult> {\n if (!secret) {\n return { status: 401, body: { error: 'unauthorized', message: 'GitLab webhook secret is not configured' } };\n }\n\n const event = normalizeHeader(c.req.header('x-gitlab-event'));\n const token = normalizeHeader(c.req.header('x-gitlab-token'));\n if (!event) return { status: 400, body: { error: 'bad_request', message: 'Missing x-gitlab-event header' } };\n if (!token) return { status: 401, body: { error: 'unauthorized', message: 'Missing x-gitlab-token header' } };\n if (!verifyGitLabToken(token, secret)) {\n return { status: 401, body: { error: 'unauthorized', message: 'Invalid GitLab webhook token' } };\n }\n\n const rawBody = await c.req.text();\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody);\n } catch {\n return { status: 400, body: { error: 'bad_request', message: 'Malformed JSON payload' } };\n }\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return { status: 400, body: { error: 'bad_request', message: 'Payload must be a JSON object' } };\n }\n const deliveryId =\n normalizeHeader(c.req.header('webhook-id')) ??\n normalizeHeader(c.req.header('idempotency-key')) ??\n normalizeHeader(c.req.header('x-gitlab-webhook-uuid')) ??\n normalizeHeader(c.req.header('x-gitlab-event-uuid')) ??\n createHash('sha256').update(event).update('\\0').update(rawBody).digest('hex');\n const instanceHost = normalizeInstanceHost(c.req.header('x-gitlab-instance'));\n return {\n event,\n deliveryId,\n ...(instanceHost ? { instanceHost } : {}),\n payload: payload as Record<string, unknown>,\n };\n}\n\nfunction getObject(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction getNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nexport function normalizeGitLabWebhookMetadata(parsed: ParsedGitLabWebhook): GitLabWebhookMetadata {\n const project = getObject(parsed.payload.project);\n const attributes = getObject(parsed.payload.object_attributes);\n const issue = getObject(parsed.payload.issue);\n const mergeRequest = getObject(parsed.payload.merge_request);\n const user = getObject(parsed.payload.user);\n const objectIid = getNumber(attributes?.iid);\n const noteableType = getString(attributes?.noteable_type);\n return {\n event: parsed.event,\n projectId: getNumber(project?.id),\n projectPath: getString(project?.path_with_namespace),\n issueIid:\n parsed.event === 'Issue Hook'\n ? objectIid\n : noteableType === 'Issue'\n ? (getNumber(issue?.iid) ?? objectIid)\n : undefined,\n mergeRequestIid:\n parsed.event === 'Merge Request Hook'\n ? objectIid\n : noteableType === 'MergeRequest'\n ? (getNumber(mergeRequest?.iid) ?? objectIid)\n : undefined,\n noteableType,\n sender: getString(parsed.payload.user_username) ?? getString(user?.username) ?? getString(user?.name),\n };\n}\n\n/**\n * Build the parsed form of a GitLab webhook from its raw body alone, for\n * deliveries that arrive without HTTP headers. The event name comes from\n * `object_kind`; the caller supplies a delivery id that is stable across\n * replays so rules replay handling and session dedupe keys behave as they do\n * for a direct delivery. Returns null when the body is not a webhook object or\n * its kind is one Factory does not handle, which callers treat exactly like an\n * unsupported event header: acknowledged and dropped.\n */\nexport function parseGitLabWebhookBody(body: unknown, deliveryId: string): ParsedGitLabWebhook | null {\n const payload = getObject(body);\n const objectKind = getString(payload?.object_kind);\n const id = normalizeHeader(deliveryId);\n if (!payload || !objectKind || !id) return null;\n const event = GITLAB_OBJECT_KIND_EVENTS[objectKind];\n if (!event) return null;\n return { event, deliveryId: id, payload };\n}\n\n/**\n * Process a parsed delivery: rules ingress first, then session dispatch when a\n * controller is mounted. Ingestion failures propagate so the caller can retry\n * the delivery (GitLab redelivers on a non-2xx; the poller leaves its cursor\n * in place).\n */\nexport async function processGitLabWebhook(\n parsed: ParsedGitLabWebhook,\n options: GitLabWebhookProcessOptions,\n): Promise<GitLabWebhookResult> {\n if (!SUPPORTED_GITLAB_WEBHOOK_EVENTS.has(parsed.event)) {\n return { status: 202, body: { ok: true, ignored: true } };\n }\n\n console.info('[GitLab Webhook]', normalizeGitLabWebhookMetadata(parsed), { deliveryId: parsed.deliveryId });\n if (options.ingestFactoryEvent) await options.ingestFactoryEvent(parsed);\n if (!options.controller) return { status: 202, body: { ok: true } };\n\n const { ingestFactoryEvent: _ingest, controller, ...dispatch } = options;\n const result = await dispatchGitLabWebhook(parsed, {\n onSenderRejected: notification => {\n console.info('[GitLab Webhook] sender not authorized', {\n deliveryId: parsed.deliveryId,\n repository: notification.metadata.projectPath,\n sender: notification.metadata.sender,\n kind: notification.kind,\n });\n },\n ...dispatch,\n controller,\n });\n if (result.failed > 0) {\n console.warn(`[GitLab Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);\n }\n if (!result.ignored) console.info('[GitLab Webhook] session delivery', { deliveryId: parsed.deliveryId, ...result });\n // The rules ingress already acknowledged the event; an event no session\n // subscribes to is still a handled delivery, not an ignored one.\n return { status: 202, body: { ok: true } };\n}\n\nexport async function handleGitLabWebhook(\n c: Context,\n options: GitLabWebhookHandlerOptions,\n): Promise<GitLabWebhookResult> {\n const parsed = await parseGitLabWebhook(c, options.webhookSecret);\n if ('status' in parsed) return parsed;\n const { webhookSecret: _secret, ...process } = options;\n return processGitLabWebhook(parsed, process);\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { IssueReconcileWorker } from "../issue-reconcile-worker.js";
1
2
  import { IncidentioApiClient } from "./api.js";
2
3
  import { buildIncidentioRoutes } from "./routes.js";
3
4
  import { buildIncidentioAgentTools } from "./agent-tools.js";
@@ -6,7 +7,6 @@ import { createIncidentioIntake } from "./intake.js";
6
7
  import { attachIncidentioRules } from "./rules.js";
7
8
  import { attachIncidentioIssueReconciler } from "./issue-reconciler.js";
8
9
  import { incidentioReconciliationEnabled, incidentioReconciliationInterval } from "./reconciliation-config.js";
9
- import { IssueReconcileWorker } from "../issue-reconcile-worker.js";
10
10
  //#region src/integrations/incidentio/integration.ts
11
11
  const DIRECT_CONNECTION_TOKEN = "incidentio-direct-api-key";
12
12
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -1,4 +1,4 @@
1
- import type { Intake, IntakeIssue } from '../capabilities/intake.js';
1
+ import type { Intake, IntakeIssue, ResolvedIntakeDispatch } from '../capabilities/intake.js';
2
2
  import type { FactoryProject, FactoryProjectsStorage } from '../storage/domains/projects/base.js';
3
3
  import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
4
4
  export interface IssueReconcileSummary {
@@ -34,13 +34,13 @@ export interface IssueReconcilerOptions<TScope = void> {
34
34
  externalId: string;
35
35
  };
36
36
  issueId(item: WorkItemRow): string | undefined;
37
- metadata(item: WorkItemRow, issue: IntakeIssue): Record<string, unknown>;
37
+ metadata(item: WorkItemRow, issue: IntakeIssue, dispatch: ResolvedIntakeDispatch): Record<string, unknown> | Promise<Record<string, unknown>>;
38
38
  /**
39
39
  * Called when a closed issue (stateType 'completed' or 'canceled') is detected
40
40
  * on a non-terminal work item. Use this to replay the close event through
41
41
  * the provider's rules ingress.
42
42
  */
43
- onClosed?(item: WorkItemRow, issue: IntakeIssue, project: FactoryProject): Promise<void>;
43
+ onClosed?(item: WorkItemRow, issue: IntakeIssue, project: FactoryProject, dispatch: ResolvedIntakeDispatch): Promise<Record<string, unknown> | void>;
44
44
  /** Whether the item already rests in a terminal phase of its board. Unknown phases are not terminal. */
45
45
  isTerminal(item: WorkItemRow): boolean;
46
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../src/integrations/issue-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB,CAAC,MAAM;IACzC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACpD;AAED,MAAM,WAAW,sBAAsB,CAAC,MAAM,GAAG,IAAI;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,cAAc,CAAC,CAAC,IAAI,EAAE,WAAW,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,wGAAwG;IACxG,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC;CACxC;AAED,MAAM,MAAM,eAAe,CAAC,MAAM,GAAG,IAAI,IAAI,MAAM,SAAS,IAAI,GAC5D,MAAM,OAAO,CAAC,qBAAqB,CAAC,GACpC,CAAC,KAAK,EAAE,mBAAmB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAsC3E,wBAAgB,qBAAqB,CAAC,MAAM,GAAG,IAAI,EACjD,OAAO,EAAE,sBAAsB,CAAC,MAAM,CAAC,GACtC,eAAe,CAAC,MAAM,CAAC,CA4EzB"}
1
+ {"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../src/integrations/issue-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB,CAAC,MAAM;IACzC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACpD;AAED,MAAM,WAAW,sBAAsB,CAAC,MAAM,GAAG,IAAI;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,cAAc,CAAC,CAAC,IAAI,EAAE,WAAW,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CACN,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,sBAAsB,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D;;;;OAIG;IACH,QAAQ,CAAC,CACP,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,sBAAsB,GAC/B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3C,wGAAwG;IACxG,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC;CACxC;AAED,MAAM,MAAM,eAAe,CAAC,MAAM,GAAG,IAAI,IAAI,MAAM,SAAS,IAAI,GAC5D,MAAM,OAAO,CAAC,qBAAqB,CAAC,GACpC,CAAC,KAAK,EAAE,mBAAmB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAyD3E,wBAAgB,qBAAqB,CAAC,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,sBAAsB,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAqFrH"}
@@ -6,6 +6,14 @@ function sameValue(left, right) {
6
6
  const rightStrings = right.filter((value) => typeof value === "string").slice().sort();
7
7
  return leftStrings.length === rightStrings.length && leftStrings.every((value, index) => value === rightStrings[index]);
8
8
  }
9
+ if (right !== null && typeof right === "object") {
10
+ if (left === null || typeof left !== "object" || Array.isArray(left)) return false;
11
+ const leftRecord = left;
12
+ const rightRecord = right;
13
+ const leftKeys = Object.keys(leftRecord);
14
+ const rightKeys = Object.keys(rightRecord);
15
+ return leftKeys.length === rightKeys.length && rightKeys.every((key) => Object.hasOwn(leftRecord, key) && sameValue(leftRecord[key], rightRecord[key]));
16
+ }
9
17
  return left === right;
10
18
  }
11
19
  function metadataMatches(current, desired) {
@@ -66,11 +74,23 @@ function createIssueReconciler(options) {
66
74
  continue;
67
75
  }
68
76
  if ((issue.stateType === "completed" || issue.stateType === "canceled") && !options.isTerminal(item) && options.onClosed) {
69
- await options.onClosed(item, issue, project);
77
+ const closedMetadata = withoutUndefined(await options.onClosed(item, issue, project, resolved) ?? {});
78
+ if (!metadataMatches(item.metadata, closedMetadata)) {
79
+ await options.storage.update({
80
+ orgId: project.orgId,
81
+ id: item.id,
82
+ userId: "factory-rule-dispatcher",
83
+ patch: { metadata: {
84
+ ...item.metadata ?? {},
85
+ ...closedMetadata
86
+ } }
87
+ });
88
+ summary.updated += 1;
89
+ }
70
90
  summary.closed += 1;
71
91
  continue;
72
92
  }
73
- const metadata = withoutUndefined(options.metadata(item, issue));
93
+ const metadata = withoutUndefined(await options.metadata(item, issue, resolved));
74
94
  if (metadataMatches(item.metadata, metadata)) continue;
75
95
  await options.storage.update({
76
96
  orgId: project.orgId,
@@ -1 +1 @@
1
- {"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../src/integrations/issue-reconciler.ts"],"sourcesContent":["import type { Intake, IntakeIssue } from '../capabilities/intake.js';\nimport type { FactoryProject, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\nexport interface IssueReconcileSummary {\n projects: number;\n checked: number;\n updated: number;\n closed: number;\n missing: number;\n failed: number;\n errors: Array<{ projectId: string; workItemId?: string; error: string }>;\n}\n\n/**\n * Scope passed in by the caller. Providers that reconcile per-repository (like\n * GitHub, sharing target discovery with the PR reconciler) pass a target set\n * and a `matches(item, scope)` predicate. Providers with no repository concept\n * (Linear) pass no scope and every issue item is considered.\n */\nexport interface IssueReconcileScope<TScope> {\n scopes: TScope[];\n matches(item: WorkItemRow, scope: TScope): boolean;\n}\n\nexport interface IssueReconcilerOptions<TScope = void> {\n integrationId: string;\n intake: Intake;\n projects: Pick<FactoryProjectsStorage, 'listAll'>;\n storage: WorkItemsStorage;\n externalSource?(item: WorkItemRow): { type: string; externalId: string };\n issueId(item: WorkItemRow): string | undefined;\n metadata(item: WorkItemRow, issue: IntakeIssue): Record<string, unknown>;\n /**\n * Called when a closed issue (stateType 'completed' or 'canceled') is detected\n * on a non-terminal work item. Use this to replay the close event through\n * the provider's rules ingress.\n */\n onClosed?(item: WorkItemRow, issue: IntakeIssue, project: FactoryProject): Promise<void>;\n /** Whether the item already rests in a terminal phase of its board. Unknown phases are not terminal. */\n isTerminal(item: WorkItemRow): boolean;\n}\n\nexport type IssueReconciler<TScope = void> = TScope extends void\n ? () => Promise<IssueReconcileSummary>\n : (scope: IssueReconcileScope<TScope>) => Promise<IssueReconcileSummary>;\n\nfunction sameValue(left: unknown, right: unknown): boolean {\n if (Array.isArray(right)) {\n if (!Array.isArray(left)) return right.length === 0;\n const leftStrings = left.filter((value): value is string => typeof value === 'string').slice().sort();\n const rightStrings = right.filter((value): value is string => typeof value === 'string').slice().sort();\n return leftStrings.length === rightStrings.length && leftStrings.every((value, index) => value === rightStrings[index]);\n }\n return left === right;\n}\n\nfunction metadataMatches(current: Record<string, unknown> | null, desired: Record<string, unknown>): boolean {\n return Object.entries(desired).every(([key, value]) => sameValue(current?.[key], value));\n}\n\n/**\n * Drop `undefined` entries so we never spread them into stored metadata. An\n * `undefined` field on the desired patch means \"no signal from the live issue\"\n * — we must preserve whatever was already stored, not clobber it.\n */\nfunction withoutUndefined(record: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\nfunction issueItems(project: FactoryProject, items: WorkItemRow[], integrationId: string): WorkItemRow[] {\n return items.filter(\n item =>\n item.factoryProjectId === project.id &&\n item.externalSource?.integrationId === integrationId &&\n item.externalSource.type === 'issue',\n );\n}\n\nexport function createIssueReconciler<TScope = void>(\n options: IssueReconcilerOptions<TScope>,\n): IssueReconciler<TScope> {\n const run = async (scope?: IssueReconcileScope<TScope>): Promise<IssueReconcileSummary> => {\n const summary: IssueReconcileSummary = {\n projects: 0,\n checked: 0,\n updated: 0,\n closed: 0,\n missing: 0,\n failed: 0,\n errors: [],\n };\n\n // A scoped sweep with zero targets is a no-op: the caller is telling us\n // there are no configured repositories to consider.\n if (scope && scope.scopes.length === 0) return summary;\n\n const projects = await options.projects.listAll();\n for (const project of projects) {\n const all = await options.storage.list({ orgId: project.orgId, factoryProjectId: project.id });\n const candidates = issueItems(project, all, options.integrationId);\n const items = scope\n ? candidates.filter(item => scope.scopes.some(target => scope.matches(item, target)))\n : candidates;\n if (items.length === 0) continue;\n summary.projects += 1;\n\n for (const item of items) {\n summary.checked += 1;\n try {\n const resolved = await options.intake.resolveIntakeDispatch?.({\n orgId: project.orgId,\n externalSource: options.externalSource?.(item) ?? item.externalSource!,\n });\n if (!resolved) {\n summary.missing += 1;\n continue;\n }\n const issueId = options.issueId(item) ?? resolved.issueId;\n const issue = await options.intake.getIssue({ ...resolved, issueId });\n if (!issue) {\n summary.missing += 1;\n continue;\n }\n\n // Close detection for Linear: replay through rules ingress if closed\n const isClosed = issue.stateType === 'completed' || issue.stateType === 'canceled';\n if (isClosed && !options.isTerminal(item) && options.onClosed) {\n await options.onClosed(item, issue, project);\n summary.closed += 1;\n continue; // Skip metadata patch for closed issues\n }\n\n const metadata = withoutUndefined(options.metadata(item, issue));\n if (metadataMatches(item.metadata, metadata)) continue;\n await options.storage.update({\n orgId: project.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { ...(item.metadata ?? {}), ...metadata } },\n });\n summary.updated += 1;\n } catch (error) {\n summary.failed += 1;\n summary.errors.push({\n projectId: project.id,\n workItemId: item.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n\n return summary;\n };\n\n return run as IssueReconciler<TScope>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../src/integrations/issue-reconciler.ts"],"sourcesContent":["import type { Intake, IntakeIssue, ResolvedIntakeDispatch } from '../capabilities/intake.js';\nimport type { FactoryProject, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\nexport interface IssueReconcileSummary {\n projects: number;\n checked: number;\n updated: number;\n closed: number;\n missing: number;\n failed: number;\n errors: Array<{ projectId: string; workItemId?: string; error: string }>;\n}\n\n/**\n * Scope passed in by the caller. Providers that reconcile per-repository (like\n * GitHub, sharing target discovery with the PR reconciler) pass a target set\n * and a `matches(item, scope)` predicate. Providers with no repository concept\n * (Linear) pass no scope and every issue item is considered.\n */\nexport interface IssueReconcileScope<TScope> {\n scopes: TScope[];\n matches(item: WorkItemRow, scope: TScope): boolean;\n}\n\nexport interface IssueReconcilerOptions<TScope = void> {\n integrationId: string;\n intake: Intake;\n projects: Pick<FactoryProjectsStorage, 'listAll'>;\n storage: WorkItemsStorage;\n externalSource?(item: WorkItemRow): { type: string; externalId: string };\n issueId(item: WorkItemRow): string | undefined;\n metadata(\n item: WorkItemRow,\n issue: IntakeIssue,\n dispatch: ResolvedIntakeDispatch,\n ): Record<string, unknown> | Promise<Record<string, unknown>>;\n /**\n * Called when a closed issue (stateType 'completed' or 'canceled') is detected\n * on a non-terminal work item. Use this to replay the close event through\n * the provider's rules ingress.\n */\n onClosed?(\n item: WorkItemRow,\n issue: IntakeIssue,\n project: FactoryProject,\n dispatch: ResolvedIntakeDispatch,\n ): Promise<Record<string, unknown> | void>;\n /** Whether the item already rests in a terminal phase of its board. Unknown phases are not terminal. */\n isTerminal(item: WorkItemRow): boolean;\n}\n\nexport type IssueReconciler<TScope = void> = TScope extends void\n ? () => Promise<IssueReconcileSummary>\n : (scope: IssueReconcileScope<TScope>) => Promise<IssueReconcileSummary>;\n\nfunction sameValue(left: unknown, right: unknown): boolean {\n if (Array.isArray(right)) {\n if (!Array.isArray(left)) return right.length === 0;\n const leftStrings = left\n .filter((value): value is string => typeof value === 'string')\n .slice()\n .sort();\n const rightStrings = right\n .filter((value): value is string => typeof value === 'string')\n .slice()\n .sort();\n return (\n leftStrings.length === rightStrings.length && leftStrings.every((value, index) => value === rightStrings[index])\n );\n }\n if (right !== null && typeof right === 'object') {\n if (left === null || typeof left !== 'object' || Array.isArray(left)) return false;\n const leftRecord = left as Record<string, unknown>;\n const rightRecord = right as Record<string, unknown>;\n const leftKeys = Object.keys(leftRecord);\n const rightKeys = Object.keys(rightRecord);\n return (\n leftKeys.length === rightKeys.length &&\n rightKeys.every(key => Object.hasOwn(leftRecord, key) && sameValue(leftRecord[key], rightRecord[key]))\n );\n }\n return left === right;\n}\n\nfunction metadataMatches(current: Record<string, unknown> | null, desired: Record<string, unknown>): boolean {\n return Object.entries(desired).every(([key, value]) => sameValue(current?.[key], value));\n}\n\n/**\n * Drop `undefined` entries so we never spread them into stored metadata. An\n * `undefined` field on the desired patch means \"no signal from the live issue\"\n * — we must preserve whatever was already stored, not clobber it.\n */\nfunction withoutUndefined(record: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\nfunction issueItems(project: FactoryProject, items: WorkItemRow[], integrationId: string): WorkItemRow[] {\n return items.filter(\n item =>\n item.factoryProjectId === project.id &&\n item.externalSource?.integrationId === integrationId &&\n item.externalSource.type === 'issue',\n );\n}\n\nexport function createIssueReconciler<TScope = void>(options: IssueReconcilerOptions<TScope>): IssueReconciler<TScope> {\n const run = async (scope?: IssueReconcileScope<TScope>): Promise<IssueReconcileSummary> => {\n const summary: IssueReconcileSummary = {\n projects: 0,\n checked: 0,\n updated: 0,\n closed: 0,\n missing: 0,\n failed: 0,\n errors: [],\n };\n\n // A scoped sweep with zero targets is a no-op: the caller is telling us\n // there are no configured repositories to consider.\n if (scope && scope.scopes.length === 0) return summary;\n\n const projects = await options.projects.listAll();\n for (const project of projects) {\n const all = await options.storage.list({ orgId: project.orgId, factoryProjectId: project.id });\n const candidates = issueItems(project, all, options.integrationId);\n const items = scope\n ? candidates.filter(item => scope.scopes.some(target => scope.matches(item, target)))\n : candidates;\n if (items.length === 0) continue;\n summary.projects += 1;\n\n for (const item of items) {\n summary.checked += 1;\n try {\n const resolved = await options.intake.resolveIntakeDispatch?.({\n orgId: project.orgId,\n externalSource: options.externalSource?.(item) ?? item.externalSource!,\n });\n if (!resolved) {\n summary.missing += 1;\n continue;\n }\n const issueId = options.issueId(item) ?? resolved.issueId;\n const issue = await options.intake.getIssue({ ...resolved, issueId });\n if (!issue) {\n summary.missing += 1;\n continue;\n }\n\n // Close detection for Linear: replay through rules ingress if closed\n const isClosed = issue.stateType === 'completed' || issue.stateType === 'canceled';\n if (isClosed && !options.isTerminal(item) && options.onClosed) {\n const closedMetadata = withoutUndefined((await options.onClosed(item, issue, project, resolved)) ?? {});\n if (!metadataMatches(item.metadata, closedMetadata)) {\n await options.storage.update({\n orgId: project.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { ...(item.metadata ?? {}), ...closedMetadata } },\n });\n summary.updated += 1;\n }\n summary.closed += 1;\n continue;\n }\n\n const metadata = withoutUndefined(await options.metadata(item, issue, resolved));\n if (metadataMatches(item.metadata, metadata)) continue;\n await options.storage.update({\n orgId: project.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { ...(item.metadata ?? {}), ...metadata } },\n });\n summary.updated += 1;\n } catch (error) {\n summary.failed += 1;\n summary.errors.push({\n projectId: project.id,\n workItemId: item.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n\n return summary;\n };\n\n return run as IssueReconciler<TScope>;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"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"}
@@ -241,6 +241,27 @@ var PlatformGithubIntegration = class {
241
241
  providerMetadata: repository.metadata
242
242
  }
243
243
  }))),
244
+ getRepositoryTarget: async ({ orgId, repositoryId }) => {
245
+ const repository = await this.storage.repositories.get({
246
+ orgId,
247
+ id: repositoryId
248
+ });
249
+ if (!repository) throw new Error("Version-control repository not found.");
250
+ const installation = await this.storage.installations.get({
251
+ orgId,
252
+ id: repository.installationId
253
+ });
254
+ if (!installation) throw new Error("Version-control installation not found.");
255
+ const installationId = parsePositiveInteger(installation.externalId);
256
+ if (installationId === null) throw new Error("GitHub installation id is invalid.");
257
+ return {
258
+ connection: {
259
+ type: "app-installation",
260
+ installationId
261
+ },
262
+ sourceId: repository.slug
263
+ };
264
+ },
244
265
  getRepositoryAccess: async ({ orgId, repositoryId }) => {
245
266
  const cacheKey = `${orgId}:${repositoryId}`;
246
267
  const cached = this.#repositoryAccessCache.get(cacheKey);