|
4 | 4 | // existed; this is the first caller that actually chains them into a real repeat-until-halted run. |
5 | 5 | // |
6 | 6 | // STRUCTURE (one cycle): kill-switch check -> real-per-repo-policy-aware run-loop boundary gate (before |
7 | | -// claiming) -> real runAttempt -> real PR-disposition poll (pr-disposition-poller.js, on a submitted outcome) |
8 | | -// -> real loop-closure summary -> real attemptLoopReentry decision. `attemptLoopReentry`'s own dequeue is the |
| 7 | +// claiming) -> real runAttempt -> real CI-status poll (ci-poller.js, #5394) + real PR-disposition poll |
| 8 | +// (pr-disposition-poller.js, on a submitted outcome) -> real loop-closure summary -> real attemptLoopReentry |
| 9 | +// decision. `attemptLoopReentry`'s own dequeue is the |
9 | 10 | // AUTHORITATIVE claim for every cycle after the first (its own doc: "if allowed -- dequeues the next |
10 | 11 | // candidate") -- this loop does not ALSO call portfolioQueue.dequeueNext() on a successful reentry, which |
11 | 12 | // would silently double-claim (the reentry's own claim would then leak as a permanently 'in_progress', never- |
@@ -35,6 +36,7 @@ import { runDiscover } from "./discover-cli.js"; |
35 | 36 | import { runAttempt } from "./attempt-cli.js"; |
36 | 37 | import { resolveAmsPolicy } from "./ams-policy.js"; |
37 | 38 | import { pollPrDisposition, classifyPrDisposition } from "./pr-disposition-poller.js"; |
| 39 | +import { pollCheckRuns } from "./ci-poller.js"; |
38 | 40 | import { recordPrOutcomeSnapshot } from "./pr-outcome.js"; |
39 | 41 | import { buildLoopClosureSummary } from "./loop-closure.js"; |
40 | 42 | import { attemptLoopReentry } from "./loop-reentry.js"; |
@@ -193,11 +195,13 @@ function zeroConvergence() { |
193 | 195 | * checkMinerKillSwitch?: typeof checkMinerKillSwitch, |
194 | 196 | * evaluateRunLoopBoundaryGate?: typeof evaluateRunLoopBoundaryGate, |
195 | 197 | * pollPrDisposition?: typeof pollPrDisposition, |
| 198 | + * pollCheckRuns?: typeof pollCheckRuns, |
196 | 199 | * recordPrOutcomeSnapshot?: typeof recordPrOutcomeSnapshot, |
197 | 200 | * buildLoopClosureSummary?: typeof buildLoopClosureSummary, |
198 | 201 | * attemptLoopReentry?: typeof attemptLoopReentry, |
199 | 202 | * attemptOptions?: Record<string, unknown>, |
200 | 203 | * prDispositionOptions?: Record<string, unknown>, |
| 204 | + * ciPollOptions?: Record<string, unknown>, |
201 | 205 | * }} [options] |
202 | 206 | * @returns {Promise<number>} |
203 | 207 | */ |
@@ -234,6 +238,7 @@ export async function runLoop(args, options = {}) { |
234 | 238 | const checkKillSwitchFn = options.checkMinerKillSwitch ?? checkMinerKillSwitch; |
235 | 239 | const evaluateBoundaryGateFn = options.evaluateRunLoopBoundaryGate ?? evaluateRunLoopBoundaryGate; |
236 | 240 | const pollPrDispositionFn = options.pollPrDisposition ?? pollPrDisposition; |
| 241 | + const pollCheckRunsFn = options.pollCheckRuns ?? pollCheckRuns; |
237 | 242 | const recordPrOutcomeSnapshotFn = options.recordPrOutcomeSnapshot ?? recordPrOutcomeSnapshot; |
238 | 243 | const buildLoopClosureSummaryFn = options.buildLoopClosureSummary ?? buildLoopClosureSummary; |
239 | 244 | const attemptLoopReentryFn = options.attemptLoopReentry ?? attemptLoopReentry; |
@@ -385,9 +390,27 @@ export async function runLoop(args, options = {}) { |
385 | 390 | let reentryOutcome = "other"; |
386 | 391 | let prNumber = null; |
387 | 392 | let prDisposition = null; |
| 393 | + let ciConclusion = null; |
388 | 394 | if (submitted) { |
389 | 395 | prNumber = parsePrNumberFromExecResult(lastResult?.execResult, claimed.repoFullName); |
390 | 396 | if (prNumber !== null) { |
| 397 | + // Real CI-status observation (#5394): recorded BEFORE the disposition poll below, so a submitted |
| 398 | + // PR's check-run state is captured even while it's still open, not just at its eventual merge/close. |
| 399 | + // gate-verdict-poller.js (#4273) was the originally preferred source for this signal but has no real |
| 400 | + // caller-reachable endpoint today (see its own header) -- ci-poller.js's real GitHub check-run |
| 401 | + // polling is the documented fallback for exactly this case. |
| 402 | + const ciStatus = await pollCheckRunsFn(claimed.repoFullName, prNumber, { |
| 403 | + githubToken, |
| 404 | + apiBaseUrl: options.apiBaseUrl, |
| 405 | + ...(options.ciPollOptions ?? {}), |
| 406 | + }); |
| 407 | + ciConclusion = ciStatus.conclusion; |
| 408 | + eventLedger.appendEvent({ |
| 409 | + type: "ci_status_observed", |
| 410 | + repoFullName: claimed.repoFullName, |
| 411 | + payload: { prNumber, conclusion: ciStatus.conclusion, checkCount: ciStatus.checks.length, source: "ci-poller" }, |
| 412 | + }); |
| 413 | + |
391 | 414 | prDisposition = await pollPrDispositionFn(claimed.repoFullName, prNumber, { |
392 | 415 | githubToken, |
393 | 416 | apiBaseUrl: options.apiBaseUrl, |
@@ -427,6 +450,7 @@ export async function runLoop(args, options = {}) { |
427 | 450 | attemptOutcome, |
428 | 451 | reentryOutcome, |
429 | 452 | prNumber, |
| 453 | + ciConclusion, |
430 | 454 | reentered: reentry.decision.reenter, |
431 | 455 | reasons: reentry.decision.reasons, |
432 | 456 | }); |
|
0 commit comments