Preflight
Issue origin
Inferred by an AI agent and not yet observed or reproduced
LoopX version or commit
bd51193
Host or runtime surface
Codex App
LoopX area
Control plane (goals, todos, quota, scheduler, registry, runtime)
Problem
ReplayPositionCost validates replay costs with _nonnegative, which only rejects values where normalized < 0. Since NaN < 0 is false, a NaN capture or restore cost passes validation and reaches proposal scoring; the resulting NaN score is then admitted because NaN < minimum_score_minutes is also false. The proposal list can therefore contain non-finite scores and sort unpredictably. The validator also accepts booleans and leaks OverflowError for integers too large to convert to float.
Minimal reproduction
Construct a ReplayPositionCost with capture_minutes=float("nan") and restore_minutes=0.0, then pass it with a valid FailureReplayObservation to plan_adaptive_replay_points. The cost is accepted and the resulting proposal can have score_minutes=nan. The current helper expression also returns nan for this input.
Expected behavior
Replay costs must be finite non-negative numbers. Reject NaN, infinities, booleans, and values that cannot be converted to float with a field-specific ValueError.
Actual behavior
The cost validator accepts NaN because its negative-only check does not reject non-finite values. Scoring then admits the NaN result because comparisons against the threshold are false.
Sanitized diagnostics
Additional context
No response
Preflight
Issue origin
Inferred by an AI agent and not yet observed or reproduced
LoopX version or commit
bd51193
Host or runtime surface
Codex App
LoopX area
Control plane (goals, todos, quota, scheduler, registry, runtime)
Problem
ReplayPositionCostvalidates replay costs with_nonnegative, which only rejects values wherenormalized < 0. SinceNaN < 0is false, a NaN capture or restore cost passes validation and reaches proposal scoring; the resulting NaN score is then admitted becauseNaN < minimum_score_minutesis also false. The proposal list can therefore contain non-finite scores and sort unpredictably. The validator also accepts booleans and leaksOverflowErrorfor integers too large to convert to float.Minimal reproduction
Construct a
ReplayPositionCostwithcapture_minutes=float("nan")andrestore_minutes=0.0, then pass it with a validFailureReplayObservationtoplan_adaptive_replay_points. The cost is accepted and the resulting proposal can havescore_minutes=nan. The current helper expression also returnsnanfor this input.Expected behavior
Replay costs must be finite non-negative numbers. Reject NaN, infinities, booleans, and values that cannot be converted to float with a field-specific
ValueError.Actual behavior
The cost validator accepts NaN because its negative-only check does not reject non-finite values. Scoring then admits the NaN result because comparisons against the threshold are false.
Sanitized diagnostics
Additional context
No response