mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(sequencing): reachability-aware claim bar + sequence_held surfacing (#681)
Three coupled claim-path bugs from the 2026-07-24 live incident, fixed at the shared root: - The edge-agnostic sequence bar phantom-held a task behind an unrelated, never-connected same-parent sibling that coincidentally shared a lower raw sequence (stamp_wave_sequence stamps from a partial per-task view). _claim_blocked_by_sequence now branches on is_batch_root_subtask: a MegaTask root-subtask (globally-computed Kahn wave, a deliberate staged-release barrier) keeps the strict rule unchanged; every other same-parent context routes through the pure sequence_blocker_id, which only blocks on a real transitive predecessor via dependency_ids UNIONED with completed_dependency_ids. A task with no same-parent dependency edge at all falls back to the raw bar unchanged (#452 preserved). - The hold surfaced as claim()'s bare None and was misdiagnosed by the verb runner as a concurrent-transition invalid_state. New sequence_hold_reason + a proactive _sequencing_claim_guard return a dedicated Envelope.sequence_held naming the blocker, on both the PENDING and NEEDS_REVISION reclaim paths. - give_me_work offered tasks the claim gate then rejected: both offer paths (list_pending_for_agent, _drop_dependency_held) now consult the bar via the exact claim predicate (is_pending_claim_blocked, extended to NEEDS_REVISION). Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from roboco.services.sequencing import (
|
||||
by_osmosis_tail_dev_tasks,
|
||||
cell_task_wave_chain_depends_on,
|
||||
dev_task_collision_edges,
|
||||
sequence_blocker_id,
|
||||
)
|
||||
|
||||
|
||||
@@ -594,3 +595,75 @@ def test_declared_cycle_rejected() -> None:
|
||||
]
|
||||
with pytest.raises(SequencingError):
|
||||
SequencingService().analyze(s, _backend, {"backend": 2})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# sequence_blocker_id — the claim-gate's non-batch reachability decision
|
||||
# (the 2026-07-24 phantom cross-stream serialization fix).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_sequence_blocker_no_graph_info_falls_back_to_raw_first_candidate() -> None:
|
||||
"""No dependency edge onto ANY same-parent sibling at all — the #452
|
||||
manually-sequenced, edge-less scenario — keeps the strict raw bar: the
|
||||
first (lowest-sequence) candidate blocks unconditionally."""
|
||||
a, b = uuid4(), uuid4()
|
||||
assert (
|
||||
sequence_blocker_id(
|
||||
task_dependency_ids=[],
|
||||
candidate_ids=[a, b],
|
||||
sibling_dependency_ids={a: [], b: []},
|
||||
)
|
||||
== a
|
||||
)
|
||||
|
||||
|
||||
def test_sequence_blocker_ignores_unconnected_sibling() -> None:
|
||||
"""A same-parent sibling reachable via NO edge (a different stream) must
|
||||
not block once real graph info exists elsewhere."""
|
||||
real_predecessor, unrelated = uuid4(), uuid4()
|
||||
assert (
|
||||
sequence_blocker_id(
|
||||
task_dependency_ids=[real_predecessor],
|
||||
candidate_ids=[unrelated],
|
||||
sibling_dependency_ids={real_predecessor: [], unrelated: []},
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_sequence_blocker_finds_direct_predecessor() -> None:
|
||||
predecessor = uuid4()
|
||||
assert (
|
||||
sequence_blocker_id(
|
||||
task_dependency_ids=[predecessor],
|
||||
candidate_ids=[predecessor],
|
||||
sibling_dependency_ids={predecessor: []},
|
||||
)
|
||||
== predecessor
|
||||
)
|
||||
|
||||
|
||||
def test_sequence_blocker_transitive_two_hop() -> None:
|
||||
"""A candidate two hops away (through an already-terminal, non-candidate
|
||||
intermediate) is still found — a genuine ordering must still hold."""
|
||||
intermediate, root_blocker = uuid4(), uuid4()
|
||||
assert (
|
||||
sequence_blocker_id(
|
||||
task_dependency_ids=[intermediate],
|
||||
candidate_ids=[root_blocker],
|
||||
sibling_dependency_ids={intermediate: [root_blocker], root_blocker: []},
|
||||
)
|
||||
== root_blocker
|
||||
)
|
||||
|
||||
|
||||
def test_sequence_blocker_no_candidates_is_none() -> None:
|
||||
assert (
|
||||
sequence_blocker_id(
|
||||
task_dependency_ids=[uuid4()],
|
||||
candidate_ids=[],
|
||||
sibling_dependency_ids={},
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user