@cat-factory/integrations 0.101.3 → 0.102.0
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/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/environments/EnvironmentHandlerSeeder.js +0 -0
- package/dist/modules/environments/EnvironmentHandlerSeeder.js.map +1 -1
- package/dist/modules/environments/EnvironmentProvisioningService.js +1 -1
- package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -1
- package/dist/modules/tasks/GitHubIssuesProvider.d.ts +6 -0
- package/dist/modules/tasks/GitHubIssuesProvider.d.ts.map +1 -1
- package/dist/modules/tasks/GitHubIssuesProvider.js +7 -0
- package/dist/modules/tasks/GitHubIssuesProvider.js.map +1 -1
- package/dist/modules/tasks/JiraProvider.d.ts +6 -0
- package/dist/modules/tasks/JiraProvider.d.ts.map +1 -1
- package/dist/modules/tasks/JiraProvider.js +7 -0
- package/dist/modules/tasks/JiraProvider.js.map +1 -1
- package/dist/modules/tasks/LinearTaskProvider.d.ts +6 -0
- package/dist/modules/tasks/LinearTaskProvider.d.ts.map +1 -1
- package/dist/modules/tasks/LinearTaskProvider.js +7 -0
- package/dist/modules/tasks/LinearTaskProvider.js.map +1 -1
- package/dist/modules/tasks/TaskConnectionService.d.ts +63 -1
- package/dist/modules/tasks/TaskConnectionService.d.ts.map +1 -1
- package/dist/modules/tasks/TaskConnectionService.js +156 -2
- package/dist/modules/tasks/TaskConnectionService.js.map +1 -1
- package/dist/modules/tasks/TrackerWebhookService.d.ts +102 -0
- package/dist/modules/tasks/TrackerWebhookService.d.ts.map +1 -0
- package/dist/modules/tasks/TrackerWebhookService.js +245 -0
- package/dist/modules/tasks/TrackerWebhookService.js.map +1 -0
- package/dist/modules/tasks/intakeMatch.logic.d.ts +21 -0
- package/dist/modules/tasks/intakeMatch.logic.d.ts.map +1 -0
- package/dist/modules/tasks/intakeMatch.logic.js +43 -0
- package/dist/modules/tasks/intakeMatch.logic.js.map +1 -0
- package/dist/modules/tasks/webhook/adapters.d.ts +31 -0
- package/dist/modules/tasks/webhook/adapters.d.ts.map +1 -0
- package/dist/modules/tasks/webhook/adapters.js +259 -0
- package/dist/modules/tasks/webhook/adapters.js.map +1 -0
- package/dist/modules/tasks/webhook/hmac.d.ts +34 -0
- package/dist/modules/tasks/webhook/hmac.d.ts.map +1 -0
- package/dist/modules/tasks/webhook/hmac.js +98 -0
- package/dist/modules/tasks/webhook/hmac.js.map +1 -0
- package/dist/modules/writeback/IssueWritebackService.d.ts +17 -1
- package/dist/modules/writeback/IssueWritebackService.d.ts.map +1 -1
- package/dist/modules/writeback/IssueWritebackService.js +21 -1
- package/dist/modules/writeback/IssueWritebackService.js.map +1 -1
- package/dist/modules/writeback/reviewQuestions.logic.d.ts.map +1 -1
- package/dist/modules/writeback/reviewQuestions.logic.js +6 -1
- package/dist/modules/writeback/reviewQuestions.logic.js.map +1 -1
- package/dist/modules/writeback/reviewReplies.logic.d.ts +90 -0
- package/dist/modules/writeback/reviewReplies.logic.d.ts.map +1 -0
- package/dist/modules/writeback/reviewReplies.logic.js +262 -0
- package/dist/modules/writeback/reviewReplies.logic.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { getErrorMessage, redactSecrets, TRACKER_COMMENT_INGEST_CLAIM_TTL_MS, TRACKER_WEBHOOK_REPLY_ALLOW_KEY, } from '@cat-factory/kernel';
|
|
2
|
+
import { isAllowedReplyAuthor, isPlatformAuthoredComment, parseReviewReplyCommands, } from '../writeback/reviewReplies.logic.js';
|
|
3
|
+
/** Longest reply text stored against a finding, after scrubbing. */
|
|
4
|
+
const MAX_REPLY_CHARS = 4_000;
|
|
5
|
+
export class TrackerWebhookService {
|
|
6
|
+
deps;
|
|
7
|
+
constructor(deps) {
|
|
8
|
+
this.deps = deps;
|
|
9
|
+
}
|
|
10
|
+
/** Route one verified, parsed delivery. */
|
|
11
|
+
async handle(workspaceId, event) {
|
|
12
|
+
return event.kind === 'issue'
|
|
13
|
+
? this.handleIssue(workspaceId, event)
|
|
14
|
+
: this.handleComment(workspaceId, event);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An issue moved on the tracker: fire any recurring intake schedule it qualifies for.
|
|
18
|
+
*
|
|
19
|
+
* Nothing is imported here. The fired run's `bug-intake` step does the searching, dedup,
|
|
20
|
+
* importing, linking and claiming through the unchanged `BugIntakeService`, so a push-driven
|
|
21
|
+
* pickup is byte-for-byte a cadence pickup that simply happened sooner.
|
|
22
|
+
*/
|
|
23
|
+
async handleIssue(workspaceId, event) {
|
|
24
|
+
if (!this.deps.triggerIntake)
|
|
25
|
+
return { kind: 'ignored', reason: 'intake_not_wired' };
|
|
26
|
+
const fired = await this.deps.triggerIntake(workspaceId, event);
|
|
27
|
+
return { kind: 'intake', fired };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A comment landed on a tracked issue: apply any review commands it carries.
|
|
31
|
+
*
|
|
32
|
+
* The guard ORDER is load-bearing and runs cheapest-and-most-silencing first:
|
|
33
|
+
*
|
|
34
|
+
* 1. **No commands ⇒ ignore, before anything else.** Most comments on a linked issue are
|
|
35
|
+
* ordinary human discussion; reading the projection for each of them would make every
|
|
36
|
+
* conversation cost a database round trip.
|
|
37
|
+
* 2. **Bot / unauthorized author ⇒ drop SILENTLY** — no state change and, deliberately, no ack.
|
|
38
|
+
* Replying would confirm the hook exists and hand an attacker an oracle (D7). The bot half
|
|
39
|
+
* also stops the platform's own ack comment from feeding itself.
|
|
40
|
+
* 3. Only then the projection lookup, the review lookup, and the claim.
|
|
41
|
+
*/
|
|
42
|
+
async handleComment(workspaceId, event) {
|
|
43
|
+
const gateway = this.deps.reviewGateway;
|
|
44
|
+
const markers = this.deps.commentIngestRepository;
|
|
45
|
+
if (!gateway || !markers)
|
|
46
|
+
return { kind: 'ignored', reason: 'replies_not_wired' };
|
|
47
|
+
// Our OWN comments, refused structurally before anything else. The author-side bot check below
|
|
48
|
+
// catches them on GitHub and Jira, but Linear flags no bots and the default allow-list admits
|
|
49
|
+
// any author — so on Linear an acknowledgement is an allowed author commenting on an issue we
|
|
50
|
+
// are linked to, and an ack that could re-enter its own ingest is an unbounded comment loop
|
|
51
|
+
// rather than a duplicate (each ack carries a fresh comment id, so the claim cannot stop it).
|
|
52
|
+
// Cheap, vendor-independent, and it holds whatever a future renderer writes.
|
|
53
|
+
if (isPlatformAuthoredComment(event.body))
|
|
54
|
+
return { kind: 'ignored', reason: 'self_authored' };
|
|
55
|
+
const commands = parseReviewReplyCommands(event.body);
|
|
56
|
+
if (commands.length === 0)
|
|
57
|
+
return { kind: 'ignored', reason: 'no_commands' };
|
|
58
|
+
const connection = await this.deps.taskConnectionRepository.getByWorkspace(workspaceId, event.source);
|
|
59
|
+
const allowList = connection?.credentials?.[TRACKER_WEBHOOK_REPLY_ALLOW_KEY] ?? '';
|
|
60
|
+
if (!isAllowedReplyAuthor(event.author, allowList)) {
|
|
61
|
+
// The one path that leaves NO other trace, so it must leave a log line.
|
|
62
|
+
this.deps.log?.({
|
|
63
|
+
msg: 'tracker reply ignored: author not allowed',
|
|
64
|
+
workspaceId,
|
|
65
|
+
source: event.source,
|
|
66
|
+
externalId: event.externalId,
|
|
67
|
+
author: event.author.handle,
|
|
68
|
+
bot: event.author.bot,
|
|
69
|
+
});
|
|
70
|
+
return { kind: 'ignored', reason: 'author_not_allowed' };
|
|
71
|
+
}
|
|
72
|
+
const issue = await this.deps.taskRepository.get(workspaceId, event.source, event.externalId);
|
|
73
|
+
const blockId = issue?.linkedBlockId;
|
|
74
|
+
if (!blockId)
|
|
75
|
+
return { kind: 'ignored', reason: 'issue_not_linked' };
|
|
76
|
+
const review = await gateway.getForBlock(workspaceId, blockId);
|
|
77
|
+
if (!review)
|
|
78
|
+
return { kind: 'ignored', reason: 'no_review' };
|
|
79
|
+
// Claim BEFORE applying: a crash between applying an answer and writing the marker must not
|
|
80
|
+
// re-answer on the next delivery. A `failed` marker is re-claimable so a transient failure is
|
|
81
|
+
// retried; a long-abandoned `pending` one is re-claimable too, so an ingester killed mid-apply
|
|
82
|
+
// does not silence that comment forever.
|
|
83
|
+
//
|
|
84
|
+
// A claim that ERRORS is deliberately allowed to propagate rather than being read as "someone
|
|
85
|
+
// else has it". `false` means another delivery owns the ingest — a fact this one can act on —
|
|
86
|
+
// whereas a store failure means NOTHING is known and nothing was written: reporting it as
|
|
87
|
+
// already-ingested would ack the delivery, drop the reporter's answer, and leave no trace, all
|
|
88
|
+
// while looking like a successful dedup. Letting it throw hands the delivery back to the retry
|
|
89
|
+
// machinery the whole claim exists to make safe.
|
|
90
|
+
const at = this.now();
|
|
91
|
+
const key = {
|
|
92
|
+
workspaceId,
|
|
93
|
+
source: event.source,
|
|
94
|
+
externalId: event.externalId,
|
|
95
|
+
commentId: event.commentId,
|
|
96
|
+
};
|
|
97
|
+
const claimed = await markers.claim(key, {
|
|
98
|
+
now: at,
|
|
99
|
+
reclaimPendingBefore: at - TRACKER_COMMENT_INGEST_CLAIM_TTL_MS,
|
|
100
|
+
});
|
|
101
|
+
if (!claimed)
|
|
102
|
+
return { kind: 'ignored', reason: 'already_ingested' };
|
|
103
|
+
try {
|
|
104
|
+
const ack = await this.applyCommands(workspaceId, blockId, review, commands, gateway);
|
|
105
|
+
await markers.settle(key, { status: 'applied' }, this.now());
|
|
106
|
+
// Commit the state, THEN talk to the tracker: a failed ack must never look like a failed
|
|
107
|
+
// reply, and the answer is already durable by the time this runs.
|
|
108
|
+
await this.deps.issueWriteback
|
|
109
|
+
?.postReviewReplyAck(workspaceId, { source: event.source, externalId: event.externalId }, ack)
|
|
110
|
+
.catch(() => { });
|
|
111
|
+
return { kind: 'reply', outcome: ack.outcome };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const raw = getErrorMessage(error);
|
|
115
|
+
const scrubbed = (redactSecrets(raw) ?? '').slice(0, 500);
|
|
116
|
+
await markers.settle(key, { status: 'failed', error: scrubbed }, this.now()).catch(() => { });
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Apply a comment's commands to a live review, in the order written, and describe what happened.
|
|
122
|
+
*
|
|
123
|
+
* A review that has already SETTLED (`incorporated`) applies nothing — the run is no longer
|
|
124
|
+
* waiting on these questions — but still produces an ack, because a reporter who answered in
|
|
125
|
+
* good faith deserves to learn the questions moved on rather than watch their reply vanish.
|
|
126
|
+
*/
|
|
127
|
+
async applyCommands(workspaceId, blockId, review, commands, gateway) {
|
|
128
|
+
const runId = (await this.deps.resolveRunId?.(workspaceId, blockId)) ?? '';
|
|
129
|
+
if (review.status === 'incorporated') {
|
|
130
|
+
return {
|
|
131
|
+
reviewId: review.id,
|
|
132
|
+
runId,
|
|
133
|
+
outcome: 'settled',
|
|
134
|
+
answered: [],
|
|
135
|
+
dismissed: [],
|
|
136
|
+
outstanding: [],
|
|
137
|
+
rejected: [],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const byId = new Map(review.items.map((item) => [item.id, item]));
|
|
141
|
+
const answered = [];
|
|
142
|
+
const dismissed = [];
|
|
143
|
+
const rejected = [];
|
|
144
|
+
let control = null;
|
|
145
|
+
let current = review;
|
|
146
|
+
for (const command of commands) {
|
|
147
|
+
switch (command.verb) {
|
|
148
|
+
case 'answer': {
|
|
149
|
+
if (!byId.has(command.itemId)) {
|
|
150
|
+
rejected.push({ command: command.line, reason: `no finding \`${command.itemId}\`` });
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const reply = (redactSecrets(command.text) ?? '').trim().slice(0, MAX_REPLY_CHARS);
|
|
154
|
+
if (!reply) {
|
|
155
|
+
rejected.push({ command: command.line, reason: 'the answer was empty' });
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
current = await gateway.replyToItem(workspaceId, current.id, command.itemId, reply);
|
|
159
|
+
answered.push(command.itemId);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'dismiss': {
|
|
163
|
+
if (!byId.has(command.itemId)) {
|
|
164
|
+
rejected.push({ command: command.line, reason: `no finding \`${command.itemId}\`` });
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
current = await gateway.setItemStatus(workspaceId, current.id, command.itemId, 'dismissed');
|
|
168
|
+
dismissed.push(command.itemId);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'proceed':
|
|
172
|
+
// `proceed` is meaningful in BOTH states — at the cap it is one of the three choices, and
|
|
173
|
+
// outside it, it is the ordinary "settle and advance" the SPA offers — so it is the one
|
|
174
|
+
// control verb that never needs the cap. `stop` / `extra-round` only exist at the cap.
|
|
175
|
+
control = current.status === 'exceeded' ? 'proceed' : 'proceed-now';
|
|
176
|
+
break;
|
|
177
|
+
case 'stop':
|
|
178
|
+
case 'extra-round':
|
|
179
|
+
if (current.status !== 'exceeded') {
|
|
180
|
+
rejected.push({
|
|
181
|
+
command: command.line,
|
|
182
|
+
reason: 'only available once the review has used its full iteration budget',
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
control = command.verb === 'stop' ? 'stop-reset' : 'extra-round';
|
|
187
|
+
break;
|
|
188
|
+
case 'unknown':
|
|
189
|
+
rejected.push({
|
|
190
|
+
command: command.line,
|
|
191
|
+
reason: 'unrecognised command (try `answer <id> …`, `dismiss <id>`, or `proceed`)',
|
|
192
|
+
});
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (control) {
|
|
197
|
+
current =
|
|
198
|
+
control === 'proceed-now'
|
|
199
|
+
? await gateway.proceed(workspaceId, blockId)
|
|
200
|
+
: await gateway.resolveExceeded(workspaceId, blockId, control);
|
|
201
|
+
return {
|
|
202
|
+
reviewId: current.id,
|
|
203
|
+
runId,
|
|
204
|
+
outcome: 'resolved',
|
|
205
|
+
answered,
|
|
206
|
+
dismissed,
|
|
207
|
+
outstanding: [],
|
|
208
|
+
rejected,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const outstanding = current.items.filter((item) => item.status === 'open');
|
|
212
|
+
// Nothing left open ⇒ fold the answers in and let the durable driver re-review, exactly as the
|
|
213
|
+
// SPA's incorporate does. This is the D6 default that makes the ticket a complete surface: a
|
|
214
|
+
// reporter who answered everything should not also have to know to press a button.
|
|
215
|
+
if (outstanding.length === 0 && (answered.length > 0 || dismissed.length > 0)) {
|
|
216
|
+
current = await gateway.incorporate(workspaceId, blockId);
|
|
217
|
+
return {
|
|
218
|
+
reviewId: current.id,
|
|
219
|
+
runId,
|
|
220
|
+
outcome: 'incorporating',
|
|
221
|
+
answered,
|
|
222
|
+
dismissed,
|
|
223
|
+
outstanding: [],
|
|
224
|
+
rejected,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
reviewId: current.id,
|
|
229
|
+
runId,
|
|
230
|
+
outcome: current.status === 'exceeded' ? 'exceeded' : 'awaiting',
|
|
231
|
+
answered,
|
|
232
|
+
dismissed,
|
|
233
|
+
outstanding: outstanding.map(toFinding),
|
|
234
|
+
rejected,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
now() {
|
|
238
|
+
return (this.deps.clock ?? Date).now();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Project a live review item onto the finding shape the ack renders (ids verbatim, prose capped). */
|
|
242
|
+
function toFinding(item) {
|
|
243
|
+
return { id: item.id, title: item.title, detail: item.detail };
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=TrackerWebhookService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrackerWebhookService.js","sourceRoot":"","sources":["../../../src/modules/tasks/TrackerWebhookService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IssueIntakeConfig, TrackerIssueEvent } from '@cat-factory/kernel';
|
|
2
|
+
/** The intake configuration a schedule carries (`PipelineSchedule.issueIntake`). */
|
|
3
|
+
type IntakeConfig = Pick<IssueIntakeConfig, 'source' | 'predicates'>;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a pushed issue event plausibly matches a schedule's intake predicates.
|
|
6
|
+
*
|
|
7
|
+
* Matching rules, mirroring what `searchIssues` pushes into the vendor query:
|
|
8
|
+
*
|
|
9
|
+
* - **source** must be the schedule's source (an exact requirement, always evaluable).
|
|
10
|
+
* - **labels** — every configured label must be present on the issue, case-insensitively (the
|
|
11
|
+
* vendor queries are `AND`-semantics; GitHub and Jira both match labels case-insensitively).
|
|
12
|
+
* - **titleFragment** — case-insensitive substring of the issue title.
|
|
13
|
+
* - **issueType** — case-insensitive equality; a source with no issue-type notion (Linear) sends
|
|
14
|
+
* `null`, which matches by the fail-open rule above rather than excluding the whole source.
|
|
15
|
+
*
|
|
16
|
+
* A `closed` event never qualifies: intake picks up OPEN issues, and firing on a close would spend
|
|
17
|
+
* a run to discover the issue is gone.
|
|
18
|
+
*/
|
|
19
|
+
export declare function issueEventMatchesIntake(config: IntakeConfig, event: TrackerIssueEvent): boolean;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=intakeMatch.logic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intakeMatch.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/tasks/intakeMatch.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAyB/E,oFAAoF;AACpF,KAAK,YAAY,GAAG,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,YAAY,CAAC,CAAA;AAEpE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CA2B/F"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a pushed issue event plausibly matches a schedule's intake predicates.
|
|
3
|
+
*
|
|
4
|
+
* Matching rules, mirroring what `searchIssues` pushes into the vendor query:
|
|
5
|
+
*
|
|
6
|
+
* - **source** must be the schedule's source (an exact requirement, always evaluable).
|
|
7
|
+
* - **labels** — every configured label must be present on the issue, case-insensitively (the
|
|
8
|
+
* vendor queries are `AND`-semantics; GitHub and Jira both match labels case-insensitively).
|
|
9
|
+
* - **titleFragment** — case-insensitive substring of the issue title.
|
|
10
|
+
* - **issueType** — case-insensitive equality; a source with no issue-type notion (Linear) sends
|
|
11
|
+
* `null`, which matches by the fail-open rule above rather than excluding the whole source.
|
|
12
|
+
*
|
|
13
|
+
* A `closed` event never qualifies: intake picks up OPEN issues, and firing on a close would spend
|
|
14
|
+
* a run to discover the issue is gone.
|
|
15
|
+
*/
|
|
16
|
+
export function issueEventMatchesIntake(config, event) {
|
|
17
|
+
if (event.source !== config.source)
|
|
18
|
+
return false;
|
|
19
|
+
if (event.action === 'closed')
|
|
20
|
+
return false;
|
|
21
|
+
const predicates = config.predicates;
|
|
22
|
+
const labels = predicates.labels ?? [];
|
|
23
|
+
if (labels.length > 0) {
|
|
24
|
+
// An event with NO labels array at all is indistinguishable from one whose labels the vendor
|
|
25
|
+
// omitted from this payload shape, so an empty set fails open — the fired run's vendor search
|
|
26
|
+
// is the authority either way.
|
|
27
|
+
if (event.labels.length > 0) {
|
|
28
|
+
const present = new Set(event.labels.map((l) => l.toLowerCase()));
|
|
29
|
+
if (!labels.every((label) => present.has(label.toLowerCase())))
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const fragment = predicates.titleFragment?.trim();
|
|
34
|
+
if (fragment && event.title && !event.title.toLowerCase().includes(fragment.toLowerCase())) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const issueType = predicates.issueType?.trim();
|
|
38
|
+
if (issueType && event.issueType && event.issueType.toLowerCase() !== issueType.toLowerCase()) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=intakeMatch.logic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intakeMatch.logic.js","sourceRoot":"","sources":["../../../src/modules/tasks/intakeMatch.logic.ts"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAoB,EAAE,KAAwB;IACpF,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAChD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3C,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAA;IACpC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,EAAE,CAAA;IACtC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,6FAA6F;QAC7F,8FAA8F;QAC9F,+BAA+B;QAC/B,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;YACjE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAA;QAC9E,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,aAAa,EAAE,IAAI,EAAE,CAAA;IACjD,IAAI,QAAQ,IAAI,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC3F,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,CAAA;IAC9C,IAAI,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;QAC9F,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TaskSourceWebhookAdapter } from '@cat-factory/kernel';
|
|
2
|
+
/**
|
|
3
|
+
* GitHub Issues.
|
|
4
|
+
*
|
|
5
|
+
* NOTE the deliberate scope: only `issues` and `issue_comment` are consumed. A `pull_request`
|
|
6
|
+
* delivery is a VCS concern that already rides `/github/webhooks` — routing it here too would
|
|
7
|
+
* double-project it.
|
|
8
|
+
*
|
|
9
|
+
* GitHub also sends `issue_comment` for comments on PULL REQUESTS, and those are mapped here like
|
|
10
|
+
* any other. That is harmless rather than wrong: issues and PRs share one number space per repo,
|
|
11
|
+
* so a PR comment's `externalId` can never collide with a real issue's, and it simply finds no
|
|
12
|
+
* linked task and is ignored. The practical consequence is that a reply command typed on a PR does
|
|
13
|
+
* nothing — the loop is anchored to the issue the work was requested on.
|
|
14
|
+
*/
|
|
15
|
+
export declare const githubIssuesWebhookAdapter: TaskSourceWebhookAdapter;
|
|
16
|
+
/**
|
|
17
|
+
* Jira Cloud.
|
|
18
|
+
*
|
|
19
|
+
* Jira routes everything through `webhookEvent` in the BODY rather than a header, so the
|
|
20
|
+
* delivery's `eventName` is ignored here. Its label/type fields live under `issue.fields`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const jiraWebhookAdapter: TaskSourceWebhookAdapter;
|
|
23
|
+
/**
|
|
24
|
+
* Linear.
|
|
25
|
+
*
|
|
26
|
+
* Linear's `Comment` payload carries the issue's human identifier under `data.issue.identifier`
|
|
27
|
+
* — which is what {@link parseLinearRef} yields and therefore what the projection is keyed by.
|
|
28
|
+
* The raw `data.issue.id` is a UUID and would never match an imported row.
|
|
29
|
+
*/
|
|
30
|
+
export declare const linearWebhookAdapter: TaskSourceWebhookAdapter;
|
|
31
|
+
//# sourceMappingURL=adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../../src/modules/tasks/webhook/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EAGzB,MAAM,qBAAqB,CAAA;AA4B5B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,EAAE,wBA+CxC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,wBAoDhC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,wBAiDlC,CAAA"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { githubIssueExternalId } from '../github-issues.logic.js';
|
|
2
|
+
import { parseJsonBody, readObject, readString, verifyHmacSignature } from './hmac.js';
|
|
3
|
+
// The per-vendor half of tracker webhook ingest: verify a delivery's signature and map its
|
|
4
|
+
// payload onto the neutral `TrackerWebhookEvent`. Everything transport-shaped (raw body, fast
|
|
5
|
+
// ack, hand-off to the facade's queue) lives in the shared receiver; everything vendor-shaped
|
|
6
|
+
// lives here, exactly as `@cat-factory/gitlab`'s webhook mapper owns GitLab's.
|
|
7
|
+
//
|
|
8
|
+
// Two rules apply to every adapter below:
|
|
9
|
+
//
|
|
10
|
+
// - **`parse` never throws.** A tracker sends far more event kinds than we consume, and several
|
|
11
|
+
// send outright unrelated shapes (Jira's `jira:issue_updated` for a sprint field, Linear's
|
|
12
|
+
// `Reaction`). An unrecognised delivery maps to `null`, which the receiver ACKS — retrying it
|
|
13
|
+
// would just make the vendor redeliver a shape we will never act on.
|
|
14
|
+
// - **`parse` reads defensively.** The payload is attacker-influenced up to the point the
|
|
15
|
+
// signature proves it came from the vendor, and vendor-shaped-but-partial after; every read
|
|
16
|
+
// goes through the `readString`/`readObject` helpers rather than a cast.
|
|
17
|
+
/** GitHub App deliveries: HMAC-SHA256 hex in `X-Hub-Signature-256`, prefixed `sha256=`. */
|
|
18
|
+
const GITHUB_SCHEME = { header: 'x-hub-signature-256', prefix: 'sha256=' };
|
|
19
|
+
/** Jira Cloud webhook deliveries: HMAC-SHA256 hex in `X-Hub-Signature`, prefixed `sha256=`. */
|
|
20
|
+
const JIRA_SCHEME = { header: 'x-hub-signature', prefix: 'sha256=' };
|
|
21
|
+
/** Linear deliveries: bare HMAC-SHA256 hex in `Linear-Signature` (no scheme prefix). */
|
|
22
|
+
const LINEAR_SCHEME = { header: 'linear-signature', prefix: '' };
|
|
23
|
+
/**
|
|
24
|
+
* GitHub Issues.
|
|
25
|
+
*
|
|
26
|
+
* NOTE the deliberate scope: only `issues` and `issue_comment` are consumed. A `pull_request`
|
|
27
|
+
* delivery is a VCS concern that already rides `/github/webhooks` — routing it here too would
|
|
28
|
+
* double-project it.
|
|
29
|
+
*
|
|
30
|
+
* GitHub also sends `issue_comment` for comments on PULL REQUESTS, and those are mapped here like
|
|
31
|
+
* any other. That is harmless rather than wrong: issues and PRs share one number space per repo,
|
|
32
|
+
* so a PR comment's `externalId` can never collide with a real issue's, and it simply finds no
|
|
33
|
+
* linked task and is ignored. The practical consequence is that a reply command typed on a PR does
|
|
34
|
+
* nothing — the loop is anchored to the issue the work was requested on.
|
|
35
|
+
*/
|
|
36
|
+
export const githubIssuesWebhookAdapter = {
|
|
37
|
+
verify: (secret, delivery) => verifyHmacSignature(secret, delivery.raw, delivery.headers, GITHUB_SCHEME),
|
|
38
|
+
parse(delivery) {
|
|
39
|
+
const payload = parseJsonBody(delivery.raw);
|
|
40
|
+
if (!payload)
|
|
41
|
+
return null;
|
|
42
|
+
const repo = readString(payload, 'repository', 'full_name');
|
|
43
|
+
const number = readString(payload, 'issue', 'number');
|
|
44
|
+
if (!repo || !number)
|
|
45
|
+
return null;
|
|
46
|
+
const [owner, name] = repo.split('/');
|
|
47
|
+
if (!owner || !name)
|
|
48
|
+
return null;
|
|
49
|
+
const externalId = githubIssueExternalId({ owner, repo: name, number: Number(number) });
|
|
50
|
+
if (delivery.eventName === 'issue_comment') {
|
|
51
|
+
// `deleted` carries a comment we must not act on, and `edited` would re-apply an already
|
|
52
|
+
// ingested command under a new body — the claim is keyed by comment id, so an edit would
|
|
53
|
+
// be a legitimate second apply we do not want. Only a fresh comment drives the loop.
|
|
54
|
+
if (readString(payload, 'action') !== 'created')
|
|
55
|
+
return null;
|
|
56
|
+
const commentId = readString(payload, 'comment', 'id');
|
|
57
|
+
const body = readString(payload, 'comment', 'body');
|
|
58
|
+
if (!commentId || !body)
|
|
59
|
+
return null;
|
|
60
|
+
return {
|
|
61
|
+
kind: 'comment',
|
|
62
|
+
source: 'github',
|
|
63
|
+
externalId,
|
|
64
|
+
commentId,
|
|
65
|
+
body,
|
|
66
|
+
author: githubAuthor(readObject(payload, 'comment', 'user')),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (delivery.eventName === 'issues') {
|
|
70
|
+
const action = readString(payload, 'action');
|
|
71
|
+
if (!action)
|
|
72
|
+
return null;
|
|
73
|
+
return {
|
|
74
|
+
kind: 'issue',
|
|
75
|
+
source: 'github',
|
|
76
|
+
externalId,
|
|
77
|
+
action: githubIssueAction(action),
|
|
78
|
+
title: readString(payload, 'issue', 'title') ?? '',
|
|
79
|
+
labels: readNameList(payload, 'issue', 'labels'),
|
|
80
|
+
issueType: readString(payload, 'issue', 'type', 'name'),
|
|
81
|
+
url: readString(payload, 'issue', 'html_url'),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Jira Cloud.
|
|
89
|
+
*
|
|
90
|
+
* Jira routes everything through `webhookEvent` in the BODY rather than a header, so the
|
|
91
|
+
* delivery's `eventName` is ignored here. Its label/type fields live under `issue.fields`.
|
|
92
|
+
*/
|
|
93
|
+
export const jiraWebhookAdapter = {
|
|
94
|
+
verify: (secret, delivery) => verifyHmacSignature(secret, delivery.raw, delivery.headers, JIRA_SCHEME),
|
|
95
|
+
parse(delivery) {
|
|
96
|
+
const payload = parseJsonBody(delivery.raw);
|
|
97
|
+
if (!payload)
|
|
98
|
+
return null;
|
|
99
|
+
const event = readString(payload, 'webhookEvent') ?? '';
|
|
100
|
+
// Jira's key is the canonical external id, upper-cased exactly as `parseJiraRef` yields it,
|
|
101
|
+
// so the projection lookup matches an issue imported by paste or by search.
|
|
102
|
+
const externalId = readString(payload, 'issue', 'key')?.toUpperCase();
|
|
103
|
+
if (!externalId)
|
|
104
|
+
return null;
|
|
105
|
+
if (event === 'comment_created') {
|
|
106
|
+
const comment = readObject(payload, 'comment');
|
|
107
|
+
const commentId = readString(comment, 'id');
|
|
108
|
+
// Jira Cloud v3 comments are ADF; `renderedBody` is the HTML rendering and `body` may be an
|
|
109
|
+
// ADF document rather than a string. `readString` yields null for the object case, so an ADF
|
|
110
|
+
// comment simply isn't ingested — a reply is a plain-text command line, and guessing at ADF
|
|
111
|
+
// traversal here would be a second, drifting copy of the import path's normalisation.
|
|
112
|
+
const body = readString(comment, 'body');
|
|
113
|
+
if (!commentId || !body)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
kind: 'comment',
|
|
117
|
+
source: 'jira',
|
|
118
|
+
externalId,
|
|
119
|
+
commentId,
|
|
120
|
+
body,
|
|
121
|
+
author: jiraAuthor(readObject(comment, 'author')),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (event === 'jira:issue_created' || event === 'jira:issue_updated') {
|
|
125
|
+
const fields = readObject(payload, 'issue', 'fields');
|
|
126
|
+
const statusCategory = readString(fields, 'status', 'statusCategory', 'key');
|
|
127
|
+
return {
|
|
128
|
+
kind: 'issue',
|
|
129
|
+
source: 'jira',
|
|
130
|
+
externalId,
|
|
131
|
+
action: event === 'jira:issue_created'
|
|
132
|
+
? 'created'
|
|
133
|
+
: statusCategory === 'done'
|
|
134
|
+
? 'closed'
|
|
135
|
+
: 'updated',
|
|
136
|
+
title: readString(fields, 'summary') ?? '',
|
|
137
|
+
labels: readStringList(fields, 'labels'),
|
|
138
|
+
issueType: readString(fields, 'issuetype', 'name'),
|
|
139
|
+
url: readString(payload, 'issue', 'self'),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Linear.
|
|
147
|
+
*
|
|
148
|
+
* Linear's `Comment` payload carries the issue's human identifier under `data.issue.identifier`
|
|
149
|
+
* — which is what {@link parseLinearRef} yields and therefore what the projection is keyed by.
|
|
150
|
+
* The raw `data.issue.id` is a UUID and would never match an imported row.
|
|
151
|
+
*/
|
|
152
|
+
export const linearWebhookAdapter = {
|
|
153
|
+
verify: (secret, delivery) => verifyHmacSignature(secret, delivery.raw, delivery.headers, LINEAR_SCHEME),
|
|
154
|
+
parse(delivery) {
|
|
155
|
+
const payload = parseJsonBody(delivery.raw);
|
|
156
|
+
if (!payload)
|
|
157
|
+
return null;
|
|
158
|
+
const type = readString(payload, 'type');
|
|
159
|
+
const action = readString(payload, 'action');
|
|
160
|
+
const data = readObject(payload, 'data');
|
|
161
|
+
if (type === 'Comment') {
|
|
162
|
+
if (action !== 'create')
|
|
163
|
+
return null;
|
|
164
|
+
const externalId = readString(data, 'issue', 'identifier')?.toUpperCase();
|
|
165
|
+
const commentId = readString(data, 'id');
|
|
166
|
+
const body = readString(data, 'body');
|
|
167
|
+
if (!externalId || !commentId || !body)
|
|
168
|
+
return null;
|
|
169
|
+
return {
|
|
170
|
+
kind: 'comment',
|
|
171
|
+
source: 'linear',
|
|
172
|
+
externalId,
|
|
173
|
+
commentId,
|
|
174
|
+
body,
|
|
175
|
+
author: linearAuthor(readObject(data, 'user')),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (type === 'Issue') {
|
|
179
|
+
const externalId = readString(data, 'identifier')?.toUpperCase();
|
|
180
|
+
if (!externalId || (action !== 'create' && action !== 'update'))
|
|
181
|
+
return null;
|
|
182
|
+
return {
|
|
183
|
+
kind: 'issue',
|
|
184
|
+
source: 'linear',
|
|
185
|
+
externalId,
|
|
186
|
+
action: action === 'create'
|
|
187
|
+
? 'created'
|
|
188
|
+
: readString(data, 'state', 'type') === 'completed'
|
|
189
|
+
? 'closed'
|
|
190
|
+
: 'updated',
|
|
191
|
+
title: readString(data, 'title') ?? '',
|
|
192
|
+
labels: readNameList(data, 'labels'),
|
|
193
|
+
// Linear has no issue-type notion; intake's `issueType` predicate is a no-op here, the
|
|
194
|
+
// same way `searchIssues` ignores it for this source.
|
|
195
|
+
issueType: null,
|
|
196
|
+
url: readString(data, 'url'),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
/** GitHub's issue actions collapsed onto the neutral lifecycle triple. */
|
|
203
|
+
function githubIssueAction(action) {
|
|
204
|
+
if (action === 'opened')
|
|
205
|
+
return 'created';
|
|
206
|
+
if (action === 'closed')
|
|
207
|
+
return 'closed';
|
|
208
|
+
// `labeled`/`unlabeled`/`edited`/`typed`/`reopened`/`transferred` all mean "the issue changed";
|
|
209
|
+
// intake re-evaluates the predicates against the payload regardless of WHICH field moved, so
|
|
210
|
+
// collapsing them keeps the neutral event honest instead of inventing a per-vendor taxonomy.
|
|
211
|
+
return 'updated';
|
|
212
|
+
}
|
|
213
|
+
function githubAuthor(user) {
|
|
214
|
+
return {
|
|
215
|
+
id: readString(user, 'node_id'),
|
|
216
|
+
handle: readString(user, 'login'),
|
|
217
|
+
email: null,
|
|
218
|
+
// GitHub marks Apps (and the platform's own App comments) with `type: 'Bot'`.
|
|
219
|
+
bot: readString(user, 'type') === 'Bot',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function jiraAuthor(author) {
|
|
223
|
+
return {
|
|
224
|
+
id: readString(author, 'accountId'),
|
|
225
|
+
handle: readString(author, 'displayName'),
|
|
226
|
+
email: readString(author, 'emailAddress'),
|
|
227
|
+
bot: readString(author, 'accountType') === 'app',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function linearAuthor(user) {
|
|
231
|
+
return {
|
|
232
|
+
id: readString(user, 'id'),
|
|
233
|
+
handle: readString(user, 'name') ?? readString(user, 'displayName'),
|
|
234
|
+
email: readString(user, 'email'),
|
|
235
|
+
// Linear does not flag bots on the comment author, so an integration's own comments are
|
|
236
|
+
// caught by the identity allow-list rather than here. Reported honestly as `false` instead of
|
|
237
|
+
// guessed at from the name.
|
|
238
|
+
bot: false,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** Read a list of `{ name }` objects (GitHub/Linear labels) as plain names. */
|
|
242
|
+
function readNameList(payload, ...path) {
|
|
243
|
+
const list = readList(payload, path);
|
|
244
|
+
return list.map((entry) => readString(entry, 'name')).filter((v) => v != null);
|
|
245
|
+
}
|
|
246
|
+
/** Read a list of plain strings (Jira labels). */
|
|
247
|
+
function readStringList(payload, ...path) {
|
|
248
|
+
return readList(payload, path).filter((v) => typeof v === 'string');
|
|
249
|
+
}
|
|
250
|
+
function readList(payload, path) {
|
|
251
|
+
let cursor = payload;
|
|
252
|
+
for (const key of path) {
|
|
253
|
+
if (!cursor || typeof cursor !== 'object')
|
|
254
|
+
return [];
|
|
255
|
+
cursor = cursor[key];
|
|
256
|
+
}
|
|
257
|
+
return Array.isArray(cursor) ? cursor : [];
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../../../src/modules/tasks/webhook/adapters.ts"],"names":[],"mappings":"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"}
|