fix(ci): the liveness probe measured latency, and its one right answer was unreachable (REQ-354) - #941
Merged
Merged
Conversation
…r was unreachable (REQ-354) The workflow is named Runner Liveness and its own header calls queued-run age "the authoritative signal" that "directly measures the symptom". Queued age is LATENCY. A saturated pool and a dead pool both show jobs stuck in `queued`, and they need opposite responses. classify_stall already emitted `label-saturated` — but reaching it needs the runner list, which needs the `administration` scope, which is not grantable to GITHUB_TOKEN. Every scheduled probe 403s on that lookup and short-circuits at `runners-unknown`, which files an issue. The right answer existed with no path to it. 95 closed and 2 open issues carry this alert's title. Liveness needs no new scope, because it is observable from the jobs API the probe already reads. A job requiring labels L that is IN PROGRESS proves a runner carrying at least L exists and is working, so a queued job whose labels are a SUBSET of L is behind a live runner, not behind an outage. Required of EVERY queued job, not any: a mix where some labels have live siblings and others have none still alarms, so a real outage cannot hide behind one unrelated running job. When it cannot be shown for all, the answer is unchanged. The mode is deliberately distinct rather than reusing `label-saturated`. That mode means "capable runners exist and none is idle", established from the runner list, and its caller prints per-label idle counts read from that same list. Reached by this path the list is absent, so reusing it would print "idle by label: none" and imply an empty pool — the exact false alarm being removed. It is a `::notice::`, not a problem, on the same grounds as dependency-blocked. This workflow's charter, in its own words, is that "when the pool goes offline every gate queues forever with no fallback and no alarm". A pool with jobs actively running on the labels the queued jobs need is not that condition. This stays inside the charter rather than widening or narrowing it, and it is one line to reverse. The shape that filed the 96 alerts, measured today on #940: a 16-shard mutation matrix against 4 lean-mem runners serializes into four waves by construction, so shards queue past the 30-minute threshold every night. Org runners read total=12 online=12 busy=12, with lean-mem at 4 runners and 4 busy running exactly 4 shards and 5 queued behind them. Nothing was down and every runner was working. Oracle first, red on exactly one case with all four controls already green: nothing running on those labels must still alarm; a running sibling on OTHER labels proves nothing; a readable offline pool still outranks the jobs signal; dependency waits still short-circuit first. Four mutations each redden only the controls that name the property — weakening every to any, weakening superset to any overlap, counting queued jobs as live, and running the check when the runner list IS readable. REQ-351 is NOT resolved here. It proposes scoping the probe to CI Gate's needs, which changes WHICH jobs the alarm watches and is the maintainer's call. This changes nothing about which jobs are watched; it stops the probe calling a demonstrably live pool dead. Implements: REQ-354 Refs: REQ-342, REQ-343, REQ-351
📐 Rivet artifact delta
Graphgraph LR
REQ_354["REQ-354"]:::added
classDef added fill:#d4edda,stroke:#28a745,color:#155724
classDef removed fill:#f8d7da,stroke:#dc3545,color:#721c24
classDef modified fill:#fff3cd,stroke:#ffc107,color:#856404
classDef overflow fill:#e2e3e5,stroke:#6c757d,color:#495057,stroke-dasharray: 3 3
Added
Posted by |
`rivet add` writes through Bash rather than the Edit tool, so the PostToolUse auto-stamp hook never fires for artifacts created that way — the artifact recording an AI-authored change carried no AI-authorship record. Same gap as REQ-353, stamped in #942. Trace: skip
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
The workflow is named Runner Liveness. Its own header calls queued-run age "the authoritative signal" that "directly measures the symptom".
Queued age is latency. A saturated pool and a dead pool both show jobs stuck in
queued, and they need opposite responses.classify_stallalready emittedlabel-saturated— but reaching it needs the runner list, which needs theadministrationscope, which is not grantable toGITHUB_TOKEN. So every scheduled probe 403s on that lookup and short-circuits atrunners-unknown, which files an issue.The right answer existed with no path to it. Measured today:
Liveness needs no new scope
It is observable from the jobs API the probe already reads. A job requiring labels
Lthat isin_progressproves a runner carrying at leastLexists and is working. A queued job whose labels are a subset ofLis therefore behind a live runner, not behind an outage.Required of every queued job, not any — a mix where some labels have live siblings and others have none still alarms, so a real outage cannot hide behind one unrelated running job. When it cannot be shown for all, the answer is unchanged from before.
Why a distinct mode, not
label-saturatedThat mode means "capable runners exist and none is idle", established from the runner list, and its caller prints per-label idle counts read from that same list. Reached by this path the list is absent, so reusing it would print
idle by label: noneand imply an empty pool — the exact false alarm being removed.queue-behind-live-runnersnames what was actually observed.Why a notice, not a problem
Same grounds as
dependency-blocked. This workflow's charter, in its own words:A pool with jobs actively running on the labels the queued jobs need is not that condition. This stays inside the charter rather than widening or narrowing it, and it is one line to reverse.
The shape that filed 96 alerts
Measured on #940 (closed today with this evidence): a 16-shard mutation matrix against 4
lean-memrunners serializes into four waves by construction, so shards queue past the 30-minute threshold every night.Nothing was down. Every runner was working.
Verification
Oracle first, red on exactly one case with all four controls already green:
Four mutations, each reddening only the controls that name the property:
everyqueued job →anyfmt=0 ·clippy --all-targets -D warnings(1.97.0)=0 ·cargo test --workspace=0 (2390 passed) ·cli_commands=0 (216) ·rivet validate=0 ·rivet docs check=0 ·yamllint=0 ·diagnose_test=0 ·rivet coverage --testsshowsREQ-354 2 test markersNot resolved here
REQ-351 proposes scoping the probe to
CI Gate's needs — that changes which jobs the alarm watches and is the maintainer's call. This changes nothing about which jobs are watched; it stops the probe calling a demonstrably live pool dead.