@davidvornholt/standards 0.17.2 → 0.18.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/README.md +3 -3
- package/package.json +11 -1
- package/src/cli.ts +26 -11
- package/src/creds-login-github-manifest.ts +14 -7
- package/src/dependabot-directory-character-class.ts +139 -0
- package/src/dependabot-directory-normalize.ts +33 -0
- package/src/dependabot-directory-overlap.ts +139 -0
- package/src/dependabot-update.ts +32 -4
- package/src/github-api.ts +0 -80
- package/src/github-apply.ts +3 -2
- package/src/github-bypass-actors.ts +56 -0
- package/src/github-command-shared.ts +9 -2
- package/src/github-diff.ts +2 -109
- package/src/github-graphql.ts +198 -0
- package/src/github-live-drift.ts +57 -6
- package/src/github-ruleset-api.ts +57 -0
- package/src/github-ruleset-diff.ts +181 -0
- package/src/github-ruleset-visibility.ts +100 -0
- package/src/github-settings-parse.ts +12 -10
- package/src/github-settings.ts +3 -2
- package/src/poller-claim-reconciliation.ts +160 -0
- package/src/poller-claim.ts +92 -89
- package/src/poller-comment-reconciliation.ts +29 -0
- package/src/poller-fix-run.ts +13 -10
- package/src/poller-queue-marker.ts +10 -9
- package/src/poller-review-execution.ts +11 -9
- package/src/poller-review-run.ts +17 -21
- package/src/poller-schedule.ts +5 -0
- package/src/poller-status.ts +34 -19
package/src/poller-claim.ts
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { hasLabel } from './github-label-identity';
|
|
3
|
-
import { isRecord } from './github-settings-parse';
|
|
4
3
|
import { type ApprovalBinding, readApprovalBinding } from './poller-approval';
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
type ClaimMarkerBinding,
|
|
6
|
+
reconcileTrustedClaimElection,
|
|
7
|
+
reconcileTrustedClaimHistory,
|
|
8
|
+
} from './poller-claim-reconciliation';
|
|
9
|
+
import { hiddenCommentMetadata } from './poller-comment-metadata';
|
|
10
|
+
import { getIssue, lastLabelEvent } from './poller-github';
|
|
9
11
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from './poller-
|
|
16
|
-
import { createComment, deleteComment } from './poller-github-write';
|
|
12
|
+
addLabels,
|
|
13
|
+
createComment,
|
|
14
|
+
deleteComment,
|
|
15
|
+
removeLabel,
|
|
16
|
+
} from './poller-github-write';
|
|
17
|
+
import type { JobDeps } from './poller-job-shared';
|
|
17
18
|
import {
|
|
18
19
|
CLAIM_METADATA_MARKER,
|
|
19
20
|
FIX_IN_PROGRESS,
|
|
20
|
-
isTrustedRole,
|
|
21
21
|
REVIEW_IN_PROGRESS,
|
|
22
22
|
} from './poller-protocol';
|
|
23
|
+
import { inProgressLabelFor, type PollerJobKind } from './poller-queue-marker';
|
|
24
|
+
import { reconcileExistingQueuedJob } from './poller-status';
|
|
23
25
|
|
|
24
|
-
export type ClaimBinding = {
|
|
25
|
-
readonly approval: ApprovalBinding;
|
|
26
|
-
readonly claimLabel: string;
|
|
27
|
-
readonly claimEpoch: string;
|
|
26
|
+
export type ClaimBinding = ClaimMarkerBinding & {
|
|
28
27
|
readonly markerId: number;
|
|
29
28
|
};
|
|
30
29
|
|
|
31
|
-
type ClaimMarker =
|
|
30
|
+
type ClaimMarker = ClaimMarkerBinding & {
|
|
32
31
|
readonly nonce: string;
|
|
33
32
|
};
|
|
34
33
|
|
|
@@ -54,73 +53,6 @@ const markerBody = (marker: ClaimMarker): string =>
|
|
|
54
53
|
marker,
|
|
55
54
|
)}`;
|
|
56
55
|
|
|
57
|
-
const isApprovalBinding = (value: unknown): value is ApprovalBinding =>
|
|
58
|
-
isRecord(value) &&
|
|
59
|
-
typeof value.id === 'string' &&
|
|
60
|
-
typeof value.repo === 'string' &&
|
|
61
|
-
typeof value.issueNumber === 'number' &&
|
|
62
|
-
typeof value.eventId === 'number' &&
|
|
63
|
-
typeof value.label === 'string' &&
|
|
64
|
-
typeof value.actorLogin === 'string' &&
|
|
65
|
-
typeof value.approvedAt === 'string' &&
|
|
66
|
-
typeof value.target === 'string';
|
|
67
|
-
|
|
68
|
-
const parseMarker = (comment: IssueComment): ClaimMarker | null => {
|
|
69
|
-
const payload = parseHiddenCommentMetadata(
|
|
70
|
-
comment.body,
|
|
71
|
-
CLAIM_METADATA_MARKER,
|
|
72
|
-
);
|
|
73
|
-
if (!isRecord(payload)) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
const raw = payload;
|
|
77
|
-
if (
|
|
78
|
-
typeof raw.claimLabel !== 'string' ||
|
|
79
|
-
typeof raw.claimEpoch !== 'string' ||
|
|
80
|
-
typeof raw.nonce !== 'string' ||
|
|
81
|
-
!isApprovalBinding(raw.approval)
|
|
82
|
-
) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
approval: raw.approval,
|
|
87
|
-
claimLabel: raw.claimLabel,
|
|
88
|
-
claimEpoch: raw.claimEpoch,
|
|
89
|
-
nonce: raw.nonce,
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const winningMarkerId = async (
|
|
94
|
-
context: ClaimContext,
|
|
95
|
-
binding: Omit<ClaimBinding, 'markerId'>,
|
|
96
|
-
): Promise<number | null> => {
|
|
97
|
-
const comments = await listIssueComments(
|
|
98
|
-
context.token,
|
|
99
|
-
context.repo,
|
|
100
|
-
context.issueNumber,
|
|
101
|
-
);
|
|
102
|
-
let winner: number | null = null;
|
|
103
|
-
for (const comment of comments) {
|
|
104
|
-
const marker = parseMarker(comment);
|
|
105
|
-
const differs =
|
|
106
|
-
marker?.approval.id !== binding.approval.id ||
|
|
107
|
-
marker.claimLabel !== binding.claimLabel ||
|
|
108
|
-
marker.claimEpoch !== binding.claimEpoch;
|
|
109
|
-
if (!differs) {
|
|
110
|
-
// biome-ignore lint/performance/noAwaitInLoops: claim authors must be checked against their current role; the usually-one-item list is deliberately fail-closed.
|
|
111
|
-
const role = await collaboratorRole(
|
|
112
|
-
context.token,
|
|
113
|
-
context.repo,
|
|
114
|
-
comment.authorLogin,
|
|
115
|
-
);
|
|
116
|
-
if (isTrustedRole(role) && (winner === null || comment.id < winner)) {
|
|
117
|
-
winner = comment.id;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return winner;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
56
|
export const acquireClaim = async (
|
|
125
57
|
context: ClaimContext,
|
|
126
58
|
approval: ApprovalBinding,
|
|
@@ -147,14 +79,36 @@ export const acquireClaim = async (
|
|
|
147
79
|
context.issueNumber,
|
|
148
80
|
markerBody({ ...provisional, nonce }),
|
|
149
81
|
);
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
82
|
+
const election = await reconcileTrustedClaimElection(context, provisional);
|
|
83
|
+
if (election.retainedId !== markerId) {
|
|
84
|
+
if (!election.markerIds.includes(markerId)) {
|
|
85
|
+
await deleteComment(context.token, context.repo, markerId);
|
|
86
|
+
}
|
|
153
87
|
return null;
|
|
154
88
|
}
|
|
155
89
|
return { ...provisional, markerId };
|
|
156
90
|
};
|
|
157
91
|
|
|
92
|
+
export const startClaim = async (
|
|
93
|
+
deps: JobDeps,
|
|
94
|
+
issueNumber: number,
|
|
95
|
+
approval: ApprovalBinding,
|
|
96
|
+
kind: PollerJobKind,
|
|
97
|
+
): Promise<ClaimBinding | null> => {
|
|
98
|
+
await reconcileExistingQueuedJob(deps, issueNumber, approval, kind);
|
|
99
|
+
const claimLabel = inProgressLabelFor(kind);
|
|
100
|
+
await reconcileTrustedClaimHistory(
|
|
101
|
+
{ token: deps.token, repo: deps.repo, issueNumber },
|
|
102
|
+
claimLabel,
|
|
103
|
+
);
|
|
104
|
+
await addLabels(deps.token, deps.repo, issueNumber, [claimLabel]);
|
|
105
|
+
return acquireClaim(
|
|
106
|
+
{ token: deps.token, repo: deps.repo, issueNumber },
|
|
107
|
+
approval,
|
|
108
|
+
claimLabel,
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
158
112
|
export const validateClaim = async (
|
|
159
113
|
context: ClaimContext,
|
|
160
114
|
claim: ClaimBinding,
|
|
@@ -182,8 +136,57 @@ export const validateClaim = async (
|
|
|
182
136
|
if (!hasLabel(issue.labels, claim.claimLabel)) {
|
|
183
137
|
return `"${claim.claimLabel}" is no longer present`;
|
|
184
138
|
}
|
|
185
|
-
const
|
|
186
|
-
return
|
|
139
|
+
const election = await reconcileTrustedClaimElection(context, claim);
|
|
140
|
+
return election.retainedId === claim.markerId
|
|
187
141
|
? null
|
|
188
142
|
: 'claim ownership changed or could not be proven';
|
|
189
143
|
};
|
|
144
|
+
|
|
145
|
+
const errorMessage = (error: unknown): string =>
|
|
146
|
+
error instanceof Error ? error.message : String(error);
|
|
147
|
+
|
|
148
|
+
const releaseOwnedClaim = async (
|
|
149
|
+
deps: JobDeps,
|
|
150
|
+
issueNumber: number,
|
|
151
|
+
claim: ClaimBinding,
|
|
152
|
+
): Promise<void> => {
|
|
153
|
+
const context = { token: deps.token, repo: deps.repo, issueNumber };
|
|
154
|
+
const election = await reconcileTrustedClaimElection(context, claim);
|
|
155
|
+
const issue = await getIssue(deps.token, deps.repo, issueNumber);
|
|
156
|
+
const event = await lastLabelEvent(
|
|
157
|
+
deps.token,
|
|
158
|
+
deps.repo,
|
|
159
|
+
issueNumber,
|
|
160
|
+
claim.claimLabel,
|
|
161
|
+
);
|
|
162
|
+
if (
|
|
163
|
+
election.retainedId === claim.markerId &&
|
|
164
|
+
hasLabel(issue.labels, claim.claimLabel) &&
|
|
165
|
+
String(event?.id) === claim.claimEpoch
|
|
166
|
+
) {
|
|
167
|
+
await removeLabel(deps.token, deps.repo, issueNumber, claim.claimLabel);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const withClaimReleasedOnFailure = async <Result>(
|
|
172
|
+
deps: JobDeps,
|
|
173
|
+
issueNumber: number,
|
|
174
|
+
claim: ClaimBinding,
|
|
175
|
+
operation: () => Promise<Result>,
|
|
176
|
+
): Promise<Result> => {
|
|
177
|
+
try {
|
|
178
|
+
return await operation();
|
|
179
|
+
} catch (operationError) {
|
|
180
|
+
try {
|
|
181
|
+
await releaseOwnedClaim(deps, issueNumber, claim);
|
|
182
|
+
} catch (releaseError) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`${errorMessage(operationError)}; releasing the claim for an automatic retry also failed: ${errorMessage(releaseError)}`,
|
|
185
|
+
{
|
|
186
|
+
cause: releaseError,
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
throw operationError;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { deleteComment } from './poller-github-write';
|
|
2
|
+
|
|
3
|
+
type CommentContext = {
|
|
4
|
+
readonly token: string | null;
|
|
5
|
+
readonly repo: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const earliestCommentId = (
|
|
9
|
+
commentIds: ReadonlyArray<number>,
|
|
10
|
+
): number | null =>
|
|
11
|
+
commentIds.reduce<number | null>(
|
|
12
|
+
(earliest, commentId) =>
|
|
13
|
+
earliest === null || commentId < earliest ? commentId : earliest,
|
|
14
|
+
null,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const retainEarliestComment = async (
|
|
18
|
+
context: CommentContext,
|
|
19
|
+
commentIds: ReadonlyArray<number>,
|
|
20
|
+
): Promise<number | null> => {
|
|
21
|
+
const retainedId = earliestCommentId(commentIds);
|
|
22
|
+
for (const commentId of commentIds) {
|
|
23
|
+
if (commentId !== retainedId) {
|
|
24
|
+
// biome-ignore lint/performance/noAwaitInLoops: GitHub mutations are deliberately serialized to avoid secondary rate limits.
|
|
25
|
+
await deleteComment(context.token, context.repo, commentId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return retainedId;
|
|
29
|
+
};
|
package/src/poller-fix-run.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { hasLabel } from './github-label-identity';
|
|
5
5
|
import { approvalIsCurrent, issueRevision } from './poller-approval';
|
|
6
|
-
import {
|
|
6
|
+
import { startClaim, withClaimReleasedOnFailure } from './poller-claim';
|
|
7
7
|
import { runCodex } from './poller-codex';
|
|
8
8
|
import { handleNonFixedOutcome } from './poller-fix-outcome';
|
|
9
9
|
import { readSealedFixOutput } from './poller-fix-output';
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
validateFixClaim,
|
|
15
15
|
} from './poller-fix-publication';
|
|
16
16
|
import { getIssue, type IssueItem } from './poller-github';
|
|
17
|
-
import { addLabels } from './poller-github-write';
|
|
18
17
|
import {
|
|
19
18
|
failJob,
|
|
20
19
|
type JobDeps,
|
|
@@ -118,12 +117,7 @@ export const runFixJob = async (
|
|
|
118
117
|
await acknowledgeQueuedJob(deps, issue.number, preamble.approval, 'fix');
|
|
119
118
|
return result(issue.number, 'waiting for run capacity');
|
|
120
119
|
}
|
|
121
|
-
await
|
|
122
|
-
const claim = await acquireClaim(
|
|
123
|
-
{ token, repo, issueNumber: issue.number },
|
|
124
|
-
preamble.approval,
|
|
125
|
-
FIX_IN_PROGRESS,
|
|
126
|
-
);
|
|
120
|
+
const claim = await startClaim(deps, issue.number, preamble.approval, 'fix');
|
|
127
121
|
if (claim === null) {
|
|
128
122
|
return result(issue.number, 'another poller owns the claim');
|
|
129
123
|
}
|
|
@@ -138,7 +132,11 @@ export const runFixJob = async (
|
|
|
138
132
|
if (sealed !== null) {
|
|
139
133
|
assertSealedOwnership(sealed, issue.number, claim.approval.id, branch);
|
|
140
134
|
return {
|
|
141
|
-
lines: [
|
|
135
|
+
lines: [
|
|
136
|
+
await withClaimReleasedOnFailure(deps, issue.number, claim, () =>
|
|
137
|
+
publishFixedOutput(resumableJob, FIX_LABELS, sealed, null),
|
|
138
|
+
),
|
|
139
|
+
],
|
|
142
140
|
ranCodex: false,
|
|
143
141
|
};
|
|
144
142
|
}
|
|
@@ -186,8 +184,13 @@ export const runFixJob = async (
|
|
|
186
184
|
};
|
|
187
185
|
}
|
|
188
186
|
const nonFixed = await handleNonFixedOutcome(job, FIX_LABELS, outcome);
|
|
187
|
+
const message =
|
|
188
|
+
nonFixed ??
|
|
189
|
+
(await withClaimReleasedOnFailure(deps, issue.number, claim, () =>
|
|
190
|
+
finishFixedJob(job, FIX_LABELS, outcome),
|
|
191
|
+
));
|
|
189
192
|
return {
|
|
190
|
-
lines: [
|
|
193
|
+
lines: [message],
|
|
191
194
|
ranCodex: true,
|
|
192
195
|
};
|
|
193
196
|
} finally {
|
|
@@ -182,13 +182,14 @@ export const inspectQueuedAcknowledgement = async (options: {
|
|
|
182
182
|
item.number,
|
|
183
183
|
expectedLabel,
|
|
184
184
|
);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
);
|
|
185
|
+
if (event === null) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const currentCheckpointCount = checkpoints.filter(
|
|
189
|
+
(checkpoint) =>
|
|
190
|
+
checkpoint.eventId === event.id &&
|
|
191
|
+
checkpoint.actorLogin === event.actorLogin &&
|
|
192
|
+
checkpoint.approvedAt === event.createdAt,
|
|
193
|
+
).length;
|
|
194
|
+
return currentCheckpointCount === 1;
|
|
194
195
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import type
|
|
2
|
+
import { type ClaimBinding, withClaimReleasedOnFailure } from './poller-claim';
|
|
3
3
|
import type { runCodex } from './poller-codex';
|
|
4
4
|
import type { PullRequest } from './poller-github-pulls';
|
|
5
5
|
import {
|
|
@@ -95,14 +95,16 @@ export const executeReviewJob = async (options: {
|
|
|
95
95
|
}
|
|
96
96
|
return {
|
|
97
97
|
lines: [
|
|
98
|
-
await
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
await withClaimReleasedOnFailure(deps, pr.number, claim, () =>
|
|
99
|
+
finishReviewedJob({
|
|
100
|
+
deps,
|
|
101
|
+
labels,
|
|
102
|
+
pr,
|
|
103
|
+
claim,
|
|
104
|
+
workDir: workspace.dir,
|
|
105
|
+
outcome,
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
106
108
|
],
|
|
107
109
|
ranCodex: true,
|
|
108
110
|
};
|
package/src/poller-review-run.ts
CHANGED
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import { hasLabel } from './github-label-identity';
|
|
8
8
|
import { approvalIsCurrent, prRevision } from './poller-approval';
|
|
9
|
-
import {
|
|
9
|
+
import { startClaim, withClaimReleasedOnFailure } from './poller-claim';
|
|
10
10
|
import { runCodex } from './poller-codex';
|
|
11
11
|
import { getIssue, type IssueItem } from './poller-github';
|
|
12
12
|
import { getPullRequest } from './poller-github-pulls';
|
|
13
|
-
import { addLabels } from './poller-github-write';
|
|
14
13
|
import {
|
|
15
14
|
failJob,
|
|
16
15
|
type JobDeps,
|
|
@@ -86,7 +85,7 @@ export const runReviewJob = async (
|
|
|
86
85
|
};
|
|
87
86
|
}
|
|
88
87
|
const pr = await getPullRequest(token, repo, prItem.number);
|
|
89
|
-
|
|
88
|
+
const plan = await currentReviewPlan(deps, pr);
|
|
90
89
|
const readTarget = () => currentReviewTarget(token, repo, pr.number, plan);
|
|
91
90
|
const preamble = await jobPreamble(deps, currentItem, REVIEW_LABELS, {
|
|
92
91
|
approved: prRevision(
|
|
@@ -157,32 +156,29 @@ export const runReviewJob = async (
|
|
|
157
156
|
ranCodex: false,
|
|
158
157
|
};
|
|
159
158
|
}
|
|
160
|
-
await
|
|
161
|
-
const claim = await acquireClaim(
|
|
162
|
-
{ token, repo, issueNumber: pr.number },
|
|
163
|
-
preamble.approval,
|
|
164
|
-
REVIEW_IN_PROGRESS,
|
|
165
|
-
);
|
|
159
|
+
const claim = await startClaim(deps, pr.number, preamble.approval, 'review');
|
|
166
160
|
if (claim === null) {
|
|
167
161
|
return {
|
|
168
162
|
lines: [`PR #${pr.number}: another poller owns the claim`],
|
|
169
163
|
ranCodex: false,
|
|
170
164
|
};
|
|
171
165
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
plan = sealed;
|
|
175
|
-
}
|
|
176
|
-
if (plan !== null) {
|
|
166
|
+
const resumablePlan = plan ?? sealed;
|
|
167
|
+
if (resumablePlan !== null) {
|
|
177
168
|
return {
|
|
178
169
|
lines: [
|
|
179
|
-
await
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
170
|
+
await withClaimReleasedOnFailure(deps, pr.number, claim, async () => {
|
|
171
|
+
if (plan === null) {
|
|
172
|
+
await publishReviewPlan(deps, pr, claim, resumablePlan);
|
|
173
|
+
}
|
|
174
|
+
return resumeReviewedJob({
|
|
175
|
+
deps,
|
|
176
|
+
labels: REVIEW_LABELS,
|
|
177
|
+
pr,
|
|
178
|
+
claim,
|
|
179
|
+
plan: resumablePlan,
|
|
180
|
+
cloneDir: cacheClone,
|
|
181
|
+
});
|
|
186
182
|
}),
|
|
187
183
|
],
|
|
188
184
|
ranCodex: false,
|
package/src/poller-schedule.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasLabel } from './github-label-identity';
|
|
2
|
+
import { reconcileTrustedClaimHistory } from './poller-claim-reconciliation';
|
|
2
3
|
import {
|
|
3
4
|
type IssueItem,
|
|
4
5
|
lastLabelEvent,
|
|
@@ -47,6 +48,10 @@ const sweepStaleClaims = async (
|
|
|
47
48
|
? Number.POSITIVE_INFINITY
|
|
48
49
|
: now - Date.parse(event.createdAt);
|
|
49
50
|
if (ageMs > config.staleClaimHours * MS_PER_HOUR) {
|
|
51
|
+
await reconcileTrustedClaimHistory(
|
|
52
|
+
{ token, repo, issueNumber: item.number },
|
|
53
|
+
claimLabel,
|
|
54
|
+
);
|
|
50
55
|
await removeLabel(token, repo, item.number, claimLabel);
|
|
51
56
|
await createComment(
|
|
52
57
|
token,
|
package/src/poller-status.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
readApprovalBinding,
|
|
7
7
|
} from './poller-approval';
|
|
8
8
|
import { hiddenCommentMetadata } from './poller-comment-metadata';
|
|
9
|
+
import { retainEarliestComment } from './poller-comment-reconciliation';
|
|
9
10
|
import { getIssue } from './poller-github';
|
|
10
11
|
import { getPullRequest } from './poller-github-pulls';
|
|
11
12
|
import { createComment, deleteComment } from './poller-github-write';
|
|
@@ -94,16 +95,14 @@ export const acknowledgeQueuedJob = async (
|
|
|
94
95
|
) {
|
|
95
96
|
return false;
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
).length > 0
|
|
106
|
-
) {
|
|
98
|
+
const existingMarkerIds = await matchingTrustedQueueCommentIds(
|
|
99
|
+
deps,
|
|
100
|
+
issueNumber,
|
|
101
|
+
expected.approvalId,
|
|
102
|
+
kind,
|
|
103
|
+
);
|
|
104
|
+
if (existingMarkerIds.length > 0) {
|
|
105
|
+
await retainEarliestComment(deps, existingMarkerIds);
|
|
107
106
|
return false;
|
|
108
107
|
}
|
|
109
108
|
const markerId = await createComment(
|
|
@@ -115,16 +114,17 @@ export const acknowledgeQueuedJob = async (
|
|
|
115
114
|
expected,
|
|
116
115
|
)}`,
|
|
117
116
|
);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
].sort((left, right) => left - right);
|
|
117
|
+
const markerIds = await matchingTrustedQueueCommentIds(
|
|
118
|
+
deps,
|
|
119
|
+
issueNumber,
|
|
120
|
+
expected.approvalId,
|
|
121
|
+
kind,
|
|
122
|
+
);
|
|
123
|
+
const winner = await retainEarliestComment(deps, markerIds);
|
|
126
124
|
if (winner !== markerId) {
|
|
127
|
-
|
|
125
|
+
if (!markerIds.includes(markerId)) {
|
|
126
|
+
await deleteComment(deps.token, deps.repo, markerId);
|
|
127
|
+
}
|
|
128
128
|
return false;
|
|
129
129
|
}
|
|
130
130
|
if (!(await isStillQueueable(deps, issueNumber, approval, kind))) {
|
|
@@ -133,3 +133,18 @@ export const acknowledgeQueuedJob = async (
|
|
|
133
133
|
}
|
|
134
134
|
return true;
|
|
135
135
|
};
|
|
136
|
+
|
|
137
|
+
export const reconcileExistingQueuedJob = async (
|
|
138
|
+
deps: JobDeps,
|
|
139
|
+
issueNumber: number,
|
|
140
|
+
approval: ApprovalBinding,
|
|
141
|
+
kind: PollerJobKind,
|
|
142
|
+
): Promise<void> => {
|
|
143
|
+
const markerIds = await matchingTrustedQueueCommentIds(
|
|
144
|
+
deps,
|
|
145
|
+
issueNumber,
|
|
146
|
+
approval.id,
|
|
147
|
+
kind,
|
|
148
|
+
);
|
|
149
|
+
await retainEarliestComment(deps, markerIds);
|
|
150
|
+
};
|