refactor(hm-pipeline-ir): timeout_seconds uses Option<u32> where 0 is a meaningless / footgun value#125
Merged
markovejnovic merged 2 commits intoJun 10, 2026
Conversation
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 smell
CommandStep::timeout_seconds(graph.rs:32) andPipelineGraph::timeout_seconds(graph.rs:92) were bothOption<u32>. A literal0is fully representable but semantically nonsense — a 0-second budget means "kill the step/build immediately". Consumers got no guarantee the value was positive;scheduler.rs:447had to hand-roll aSome(secs) if secs > 0runtime guard, and nothing caught an absurd0budget at the wire boundary.The change
Both fields are now
Option<NonZeroU32>. serde deserializesNonZeroU32directly and rejects0at parse time, soSome(0)is unrepresentable. ThePipelineGraph::timeout_seconds()accessor now returnsOption<NonZeroU32>, positive by construction — no runtime> 0guard needed downstream.Added a
rejects_zero_pipeline_timeout_secondstest confirming"timeout_seconds": 0is rejected at deserialization. The JsonSchema snapshot updated to reflect the new invariant (minimum: 1.0instead of0.0) — note thisminimumtightening is the schema correctly advertising the>= 1constraint to authors.Type-safety pattern
This applies the "NonZero for counts that must never be zero" pattern from fd / rust-analyzer: push the
>= 1invariant into the type so divide/timeout-arming code is panic-free by construction, and an absurd 0-second budget is caught at the wire boundary rather than deep in the scheduler.CI / review notes
cargo check -p hm-pipeline-ir(plus that crate's tests) was run locally. Full CI runs on this PR.hm-pipeline-ir. Downstreamhm-execreadsinput.step.timeout_secondsdirectly (scheduler.rs:421) and applies asecs > 0guard (scheduler.rs:447); those call sites will need a follow-up adjustment (.get()/ drop the now-redundant guard) which CI on the consuming crates will surface. Marked draft for that reason — needs review of the downstream rollout.