|
1 | 1 | import type { LiveIssueSnapshot } from "./submission-freshness-check.js"; |
2 | 2 | import type { ObservedClaim } from "./claim-adjudication.js"; |
3 | 3 | import type { LocalWriteActionSpec } from "@loopover/engine"; |
4 | | - |
5 | | -export function assembleCompetingClaims( |
6 | | - snapshot: LiveIssueSnapshot | null | undefined, |
7 | | - selfPrNumber: number, |
8 | | - minerLogin: string, |
9 | | -): ObservedClaim[]; |
10 | | - |
| 4 | +/** |
| 5 | + * Assemble the real competing-claims set from a fetched LiveIssueSnapshot: every OTHER open PR referencing |
| 6 | + * the issue, excluding `selfPrNumber` and any PR authored by `minerLogin` itself (case-insensitive, mirrors |
| 7 | + * checkSubmissionFreshness's own author comparison -- a login can be echoed back with different casing). |
| 8 | + * Excluding same-author PRs is deliberate, not an edge case slipping through: a miner never competes against |
| 9 | + * its own work, so if this login somehow has ANOTHER open PR on the same issue (e.g. a retry after a crash |
| 10 | + * left a stale one behind), that PR is never treated as a competing claim to lose against -- only a genuinely |
| 11 | + * different claimant's PR can trigger a real close. |
| 12 | + * Pure given its inputs. |
| 13 | + * |
| 14 | + * @param {import("./submission-freshness-check.js").LiveIssueSnapshot | null | undefined} snapshot |
| 15 | + * @param {number} selfPrNumber |
| 16 | + * @param {string} minerLogin |
| 17 | + * @returns {import("./claim-adjudication.js").ObservedClaim[]} |
| 18 | + */ |
| 19 | +export declare function assembleCompetingClaims(snapshot: LiveIssueSnapshot | null | undefined, selfPrNumber: number, minerLogin: string): ObservedClaim[]; |
11 | 20 | export type ClaimConflictInput = { |
12 | | - repoFullName: string; |
13 | | - issueNumber: number; |
14 | | - selfPrNumber: number; |
15 | | - selfClaimedAt: string | null; |
16 | | - minerLogin: string; |
| 21 | + repoFullName: string; |
| 22 | + issueNumber: number; |
| 23 | + selfPrNumber: number; |
| 24 | + selfClaimedAt: string | null; |
| 25 | + minerLogin: string; |
17 | 26 | }; |
18 | | - |
19 | 27 | export type ClaimConflictDeps = { |
20 | | - fetchLiveIssueSnapshot: (repoFullName: string, issueNumber: number) => Promise<LiveIssueSnapshot | null>; |
21 | | - executeLocalWrite: (spec: LocalWriteActionSpec) => Promise<unknown>; |
| 28 | + fetchLiveIssueSnapshot: (repoFullName: string, issueNumber: number) => Promise<LiveIssueSnapshot | null>; |
| 29 | + executeLocalWrite: (spec: LocalWriteActionSpec) => Promise<unknown>; |
| 30 | +}; |
| 31 | +export type ClaimConflictResult = { |
| 32 | + checked: false; |
| 33 | + reason: "live_state_unavailable"; |
| 34 | +} | { |
| 35 | + checked: true; |
| 36 | + isWinner: true; |
| 37 | + winnerNumber: number | null; |
| 38 | + competingCount: number; |
| 39 | +} | { |
| 40 | + checked: true; |
| 41 | + isWinner: false; |
| 42 | + winnerNumber: number | null; |
| 43 | + competingCount: number; |
| 44 | + closeResult: unknown; |
22 | 45 | }; |
23 | | - |
24 | | -export type ClaimConflictResult = |
25 | | - | { checked: false; reason: "live_state_unavailable" } |
26 | | - | { checked: true; isWinner: true; winnerNumber: number | null; competingCount: number } |
27 | | - | { checked: true; isWinner: false; winnerNumber: number | null; competingCount: number; closeResult: unknown }; |
28 | | - |
29 | 46 | export type ClaimConflictRetryOptions = { |
30 | | - maxAttempts?: number; |
31 | | - sleepFn?: (ms: number) => Promise<unknown>; |
32 | | - backoffMs?: (attempt: number) => number; |
| 47 | + maxAttempts?: number; |
| 48 | + sleepFn?: (ms: number) => Promise<unknown>; |
| 49 | + backoffMs?: (attempt: number) => number; |
33 | 50 | }; |
34 | | - |
35 | | -export function resolveClaimConflict( |
36 | | - input: ClaimConflictInput, |
37 | | - deps: ClaimConflictDeps, |
38 | | - options?: ClaimConflictRetryOptions, |
39 | | -): Promise<ClaimConflictResult>; |
| 51 | +/** |
| 52 | + * Resolve a real claim conflict for an already-submitted PR. Fails OPEN (never closes anything) when the live |
| 53 | + * snapshot can't be fetched -- an unavailable check is not evidence of a lost claim. |
| 54 | + * |
| 55 | + * @param {{ repoFullName: string, issueNumber: number, selfPrNumber: number, selfClaimedAt: string | null, minerLogin: string }} input |
| 56 | + * @param {{ |
| 57 | + * fetchLiveIssueSnapshot: (repoFullName: string, issueNumber: number) => Promise<import("./submission-freshness-check.js").LiveIssueSnapshot | null>, |
| 58 | + * executeLocalWrite: (spec: import("@loopover/engine").LocalWriteActionSpec) => Promise<unknown>, |
| 59 | + * }} deps |
| 60 | + * @param {{ maxAttempts?: number, sleepFn?: (ms: number) => Promise<unknown>, backoffMs?: (attempt: number) => number }} [options] |
| 61 | + * Bounded retry for the live-state snapshot fetch (#6058): up to `maxAttempts` (default 3) attempts with |
| 62 | + * `backoffMs(attempt)` backoff between them, returning as soon as a competing claim is observed. Pure over |
| 63 | + * the injected `sleepFn`/`backoffMs` -- no real timers in tests. |
| 64 | + * @returns {Promise<{ |
| 65 | + * checked: boolean, |
| 66 | + * reason?: "live_state_unavailable", |
| 67 | + * isWinner?: boolean, |
| 68 | + * winnerNumber?: number | null, |
| 69 | + * competingCount?: number, |
| 70 | + * closeResult?: unknown, |
| 71 | + * }>} |
| 72 | + */ |
| 73 | +export declare function resolveClaimConflict(input: ClaimConflictInput, deps: ClaimConflictDeps, options?: ClaimConflictRetryOptions): Promise<ClaimConflictResult>; |
0 commit comments