|
139 | 139 | * has re-reviewed the newest push is transient GitHub state, not a fact |
140 | 140 | * about the diff. |
141 | 141 | * |
142 | | - * FAIL-CLOSED, EVERY PATH. `gh` missing, `gh` erroring, unparseable JSON, an |
| 142 | + * FAIL-CLOSED ON READABLE FACTS. `gh` missing, `gh` erroring, unparseable JSON, an |
143 | 143 | * empty rollup, a head SHA that will not resolve, a reviewer that passed with no |
144 | 144 | * description, a job name this parser cannot expand, a reviews walk that did not |
145 | 145 | * complete, a review whose `commit_id` is unreadable -- each exits non-zero with |
@@ -191,7 +191,11 @@ const DEFAULT_CONFIG = join(SCRIPTS_DIR, 'pr-review-signal.config.json'); |
191 | 191 | * makes `now() >= deadline` false forever: the poll would spin until the job's |
192 | 192 | * own job timeout killed it, printing nothing at all. That is the exact |
193 | 193 | * "no output, no verdict" shape this gate exists to reject, so an unreadable |
194 | | - * duration is an error rather than a silently infinite one. Zero and negatives |
| 194 | + * duration is an error rather than a silently infinite one. Exhausting that |
| 195 | + * duration while the rollup is still moving is reported as an explicit |
| 196 | + * LANE_PUBLICATION_TIMEOUT advisory: absence has not become evidence yet, and |
| 197 | + * the independent push/scheduled dirty-PR scan remains the eventual backstop. |
| 198 | + * Zero and negatives |
195 | 199 | * go the same way: a zero budget is a gate that never waits, and a zero poll |
196 | 200 | * interval is a busy loop against the API. |
197 | 201 | * |
@@ -653,6 +657,24 @@ export function evaluate({ |
653 | 657 | 'normal for a fork and is reported without failing:', |
654 | 658 | ); |
655 | 659 | for (const n of missing) lines.push(` - ${n}`); |
| 660 | + } else if (timedOut) { |
| 661 | + // A moving rollup at the deadline is UNKNOWN, not MISSING. Five live runs |
| 662 | + // in #3810 crossed the 2400 s budget while Build packages + WASM was still |
| 663 | + // queued; every named lane appeared later and largely passed. Rendering |
| 664 | + // that queue condition as MISSING_LANES makes a red check indistinguishable |
| 665 | + // from settled absence. The dirty-PR scan independently rechecks actual |
| 666 | + // missing lanes on main pushes and schedule, so this is reported loudly |
| 667 | + // without pretending the code failed. |
| 668 | + lines.push( |
| 669 | + `⚠️ LANE_PUBLICATION_TIMEOUT: ${missing.length} of ${required.length} required lane(s) ` + |
| 670 | + 'had not appeared before the poll budget expired, while the rollup was still unsettled. ' + |
| 671 | + 'This is an unknown queue state, not evidence that the lanes will never run:', |
| 672 | + ); |
| 673 | + for (const n of missing) lines.push(` - ${n}`); |
| 674 | + lines.push( |
| 675 | + ' Re-run this signal for an immediate answer. The independent silent-PR scan will fail ' + |
| 676 | + 'on settled missing lanes without turning hosted-runner delay into a code failure.', |
| 677 | + ); |
656 | 678 | } else { |
657 | 679 | ok = false; |
658 | 680 | lines.push( |
|
0 commit comments