@@ -149,7 +149,6 @@ function timestamp(value: unknown, label: string): string {
149149 return value ;
150150}
151151
152- /** GitHub CLI adapter. All arguments are literal argv; no shell is involved. */
153152/**
154153 * The state GitHub gives one observed required check. A check run that completed SUCCESS, NEUTRAL or SKIPPED passes, as
155154 * GitHub treats those conclusions for required checks; a commit status passes only with SUCCESS.
@@ -165,18 +164,34 @@ function observedCheckState(check: Record<string, unknown>): 'success' | 'pendin
165164 const status = String ( check . state ?? '' ) . toUpperCase ( ) ;
166165 return status === 'SUCCESS' ? 'success' : [ 'PENDING' , 'EXPECTED' ] . includes ( status ) ? 'pending' : 'failure' ;
167166}
167+ /** A real time gh reports, or NaN for none or the zero time (year 1) it gives a run that has not started or finished. */
168+ function observedTime ( value : unknown ) : number {
169+ const at = typeof value === 'string' ? Date . parse ( value ) : NaN ;
170+ return Number . isFinite ( at ) && new Date ( at ) . getUTCFullYear ( ) > 1 ? at : NaN ;
171+ }
168172/**
169- * When an observed check run or status started, to pick the latest of several with one name. gh reports a run that has
170- * not started yet with no time or the zero time (year 1); such a run was queued after every started one, so it counts
171- * as the latest.
173+ * When an observed check run or status started, to pick the latest of several with one name, or NaN when that is not
174+ * known. A run or commit status not yet completed (queued, in progress, pending) with no start time was queued after
175+ * every started one, so it counts as the latest (+Infinity). A completed one with no start time gets NaN: its order
176+ * relative to the latest is unknown (see `observedCheckOlderThan`).
172177 */
173178function observedCheckStart ( check : Record < string , unknown > ) : number {
174- const at = typeof check . startedAt === 'string' ? Date . parse ( check . startedAt ) : NaN ;
175- return Number . isFinite ( at ) && new Date ( at ) . getUTCFullYear ( ) > 1 ? at : Number . POSITIVE_INFINITY ;
179+ const started = observedTime ( check . startedAt ) ;
180+ if ( Number . isFinite ( started ) ) return started ;
181+ return observedCheckState ( check ) === 'pending' ? Number . POSITIVE_INFINITY : NaN ;
182+ }
183+ /**
184+ * For a completed run or commit status with no start time: its completion time, before which it started, so it is
185+ * known to be older than any observation of its kind that started after that time. NaN when it has a start time, is not
186+ * completed, or has no completion time.
187+ */
188+ function observedCheckOlderThan ( check : Record < string , unknown > ) : number {
189+ return Number . isNaN ( observedCheckStart ( check ) ) ? observedTime ( check . completedAt ) : NaN ;
176190}
177191/** Among runs with the same start, the one that passes least counts, so a tie never reads as passing. */
178192const checkStateRank = { failure : 0 , pending : 1 , success : 2 } as const ;
179193
194+ /** GitHub CLI adapter. All arguments are literal argv; no shell is involved. */
180195export class GhMergeGateway implements MergeGateway , MergeQueueGateway {
181196 readonly config : GhMergeConfig ;
182197 readonly run : RunGh ;
@@ -400,10 +415,23 @@ export class GhMergeGateway implements MergeGateway, MergeQueueGateway {
400415 } ) ;
401416 if ( ! matches . length ) return { ...required , state : 'missing' as const } ;
402417 // A re-run adds a newer run (or status) with the same name; GitHub counts the latest one, so an older failure
403- // does not outrank a newer pending or passing run.
404- const latest = matches . map ( check => ( { state : observedCheckState ( check ) , at : observedCheckStart ( check ) } ) )
405- . reduce ( ( best , next ) => next . at > best . at || ( next . at === best . at && checkStateRank [ next . state ] < checkStateRank [ best . state ] ) ? next : best ) ;
406- return { ...required , state : latest . state } ;
418+ // does not outrank a newer pending or passing run. A check run and a commit status with the same name are not
419+ // re-runs of each other: GitHub requires both to pass. So the latest of each kind counts, and the one that passes
420+ // least decides.
421+ // The latest is the observation with the greatest known start (a not-completed one with no start counts as
422+ // +Infinity). A completed observation with no start time is dropped when another of its kind started (or is
423+ // not-completed with no start) strictly after its completion: it is known to be older. Otherwise its order is
424+ // unknown, so it ties with the latest, and it can only make the result pass less.
425+ const latestOfKind = ( kind : Record < string , unknown > [ ] ) => {
426+ if ( kind . length === 0 ) return null ;
427+ const seen = kind . map ( check => ( { state : observedCheckState ( check ) , at : observedCheckStart ( check ) , olderThan : observedCheckOlderThan ( check ) } ) ) ;
428+ const latest = Math . max ( ...seen . map ( check => Number . isNaN ( check . at ) ? Number . NEGATIVE_INFINITY : check . at ) ) ;
429+ return seen . filter ( check => Number . isNaN ( check . at ) ? ! ( latest > check . olderThan ) : check . at === latest )
430+ . reduce ( ( worst , next ) => checkStateRank [ next . state ] < checkStateRank [ worst . state ] ? next : worst ) . state ;
431+ } ;
432+ const states = [ latestOfKind ( matches . filter ( check => typeof check . name === 'string' ) ) , latestOfKind ( matches . filter ( check => typeof check . name !== 'string' ) ) ]
433+ . filter ( state => state !== null ) ;
434+ return { ...required , state : states . reduce ( ( worst , next ) => checkStateRank [ next ] < checkStateRank [ worst ] ? next : worst ) } ;
407435 } ) ;
408436 return { rulesKnown, atomicBaseGuard, mergeQueue, requiredChecks } ;
409437 }
0 commit comments