Repository navigation
fix(OMN-20464): an abandoned dispatch stops degrading the bus after a bounded grace - #4540
Conversation
…12687) * evidence(OMN-20464): author OCC companion for OmniNode-ai/omnibase_infra#4540 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 250d3bb533fb337456990b9e88c5814154bc7577. * evidence(OMN-20464): self-bind OCC#12687 + rebind contract_sha256 --------- Co-authored-by: omnimarket-bot <bot@omninode.ai>
There was a problem hiding this comment.
Hostile Reviewer — adversarial findings (OMN-17492)
Models succeeded: qwen3-review, gpt-oss-review
Models failed: none
New finding threads: 0
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 4
Nit-level findings suppressed: 0
The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.
Below quorum: 4 finding(s) raised by one model only (OMN-18479)
These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.
- [CRITICAL]
src/omnibase_infra/event_bus/event_bus_kafka.py(gpt-oss-review) — New required field 'abandoned_at' breaks existing OrphanedDispatch constructions | The OrphanedDispatch dataclass now requires the 'abandoned_at' attribute. Only the code path in _await_dispatch_withi - [MAJOR]
src/omnibase_infra/models/health/model_dispatch_deadline_status.py(gpt-oss-review) — ModelDispatchDeadlineStatus validation fails for callers not supplying 'degrading_orphans' | The ModelDispatchDeadlineStatus schema now requires the 'degrading_orphans' field. Existing code that build - [MINOR]
src/omnibase_infra/event_bus/event_bus_kafka.py(gpt-oss-review) — Linear scan to compute degrading orphan count on each status request | dispatch_deadline_status computes 'degrading' by iterating over all orphaned dispatches each call, resulting in O(n) work where n - [MINOR]
src/omnibase_infra/event_bus/event_bus_kafka.py(gpt-oss-review) — Potential exposure of internal loop time via health model | The 'abandoned_at' timestamp is derived from loop.time() and later exposed indirectly through ModelDispatchDeadlineStatus fields, which may
✅ Hostile Reviewer — REVIEWEDCritical findings: 3
Semantics (OMN-17492 — the model finds, thread resolution gates)
Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review: qwen3-review (Qwen3.8-27B), gpt-oss-review (gpt-oss-120b) (OMN-8468/OMN-8524/OMN-17492) |
Adds tests/integration/event_bus/test_orphan_degraded_grace_integration.py, which drives the consumer loop and reads bus health_check before and after the grace. Satisfies the Integration Test Coverage gate.
There was a problem hiding this comment.
Hostile Reviewer — adversarial findings (OMN-17492)
Models succeeded: qwen3-review, gpt-oss-review
Models failed: none
New finding threads: 6
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 5
Nit-level findings suppressed: 0
The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.
Below quorum: 5 finding(s) raised by one model only (OMN-18479)
These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.
- [CRITICAL]
src/omnibase_infra/models/health/model_dispatch_deadline_status.py(gpt-oss-review) — ModelDispatchDeadlineStatus new required field breaks existing callers | The Pydantic modelModelDispatchDeadlineStatusnow requires the fielddegrading_orphanswithout a default. Code elsewhere t - [MAJOR]
src/omnibase_infra/event_bus/event_bus_kafka.py(qwen3-review) — Race condition in degrading_orphans calculation | Thedegrading_orphanscount is computed by iterating overorphansand checkingnow - orphan.abandoned_at < grace. However,orphansis a snapsh - [MAJOR]
tests/unit/event_bus/test_omn20464_orphan_degraded_grace.py(qwen3-review) — Missing test for edge case where grace is zero or very small | The configuration allowsconsumer_dispatch_orphan_degraded_grace_secondsto be any value greater than 0.0. The tests use `GRACE_SECONDS - [MINOR]
src/omnibase_infra/services/service_runtime_health_monitor.py(qwen3-review) — Documentation inconsistency in health monitor docstring | The docstring forevaluate_dispatch_deadlineinservice_runtime_health_monitor.pyis updated to mention the grace window, but the descript - [MINOR]
src/omnibase_infra/event_bus/event_bus_kafka.py(gpt-oss-review) — Inconsistent time sources for grace calculation |abandoned_atis recorded usingloop.time()while the grace comparison usestime.monotonic(). Although both are monotonic, they are not guarantee
Findings demoted from threads (anchor rejected)
-
[CRITICAL] hostile-reviewer (qwen3-review)
Time source mismatch between abandoned_at and dispatch_deadline_status | The
abandoned_atfield is populated withloop.time()in_await_dispatch_within_deadline, butdispatch_deadline_statuscomputesnowusingtime.monotonic()inside atry/except RuntimeErrorblock.loop.time()andtime.monotonic()are not guaranteed to share the same epoch or tick rate. If the event loop's clock is not backed bytime.monotonic()(e.g., in certain embedded or custom loop implementations), the subtractionResolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[CRITICAL] hostile-reviewer (gpt-oss-review)
OrphanedDispatch missing default for new field | The dataclass OrphanedDispatch now includes a required field
abandoned_atbut only the construction in_await_dispatch_within_deadlinesupplies a value. Any other code path that instantiates OrphanedDispatch without providingabandoned_atwill raise a TypeError at runtime, breaking dispatch tracking. | Evidence: Added fieldabandoned_at: floatto OrphanedDispatch and only set it in_await_dispatch_within_deadlineviaabandoned_at=loop.time(). No dResolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[MINOR] hostile-reviewer (qwen3-review)
Integration test relies on real-time sleep, which is flaky | The integration test
test_bus_health_recovers_after_grace_while_orphan_stays_trackedusesawait asyncio.sleep(GRACE_SECONDS + 0.2)to wait for the grace period to elapse. This is a real-time sleep, which can be flaky in CI environments where the system is under load. The test should use a mock or fake clock to control time, or use a condition variable to wait for the health status to change, rather than relying on a fixed sleep duration. | EviResolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[MINOR] hostile-reviewer (qwen3-review)
O(n) scan of orphans on every health check | The
dispatch_deadline_statusmethod now performs an O(n) scan of all orphans to computedegrading_orphans. If the number of orphans is large (up toorphan_limit, which is at most 16), this is not a significant performance concern. However, if the health check is called frequently (e.g., every second by a health monitor), the repeated O(n) scan could add up. Consider maintaining a counter of degrading orphans that is updated when orphans are added or when thResolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[MINOR] hostile-reviewer (gpt-oss-review)
Linear scan for degrading orphans on each health check |
dispatch_deadline_statuscomputesdegradingby iterating over the entire_orphaned_dispatchescollection on every call. With a large number of orphans this introduces O(n) work for each health check, potentially increasing latency of health endpoints. | Evidence:degrading = sum(1 for orphan in orphans if now - orphan.abandoned_at < grace)performed each invocation. | Fix: Maintain a separate counter that increments when an orphan enters the gResolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[MINOR] hostile-reviewer (gpt-oss-review)
Flaky integration test due to real-time sleep | The new integration test
test_bus_health_recovers_after_grace_while_orphan_stays_trackedrelies onasyncio.sleep(GRACE_SECONDS + 0.2). Variability in CI environments can cause the grace window to be missed, leading to nondeterministic failures. | Evidence: Test uses actual sleep to wait for grace expiration instead of mocking time. | Fix: Replace real sleeps with a mockable time source or usefreezegun/asyncioclock manipulation to deterministically adResolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492).
Summary
Fixes runtime-effects health on the dev lane following the abandoned dispatch incident (OMN-19355). Previously, an abandoned dispatch kept the event bus in a
DEGRADEDstate for its entire lifetime. Because the container healthcheck uses--degraded-policy fail, this caused the container to remain unhealthy until it was replaced.Design Decision
The abandon path intentionally avoids cancellation. A
to_threadworker cannot be killed, and cancelling the awaiter would release the projection gate slot that the thread still occupies.Chosen Shape:
consumer_dispatch_orphan_limit).DEGRADEDstatus after a bounded grace period.UNHEALTHYregardless of orphan age.Changes
New Config Field:
consumer_dispatch_orphan_degraded_grace_secondsModelKafkaEventBusConfignext toconsumer_dispatch_orphan_limit.300.0.gt 0,le 86400.OrphanedDispatch.abandoned_at).Status Model:
ModelDispatchDeadlineStatusdegrading_orphans.unhealthyiforphaned_dispatches >= orphan_limit.degradedifdegrading_orphans > 0.healthyotherwise.Tests
tests/unit/event_bus/test_omn20464_orphan_degraded_grace.py(3 tests)300.0.test_omn19355_dispatch_deadline.pyandtest_runtime_health_dispatch_deadline_dimension_omn19355.pystill pass.ruffandmypyclean on touched modules._orphaned_dispatchesuntil it settles. Asserted by the test (orphaned_dispatchesstays 1 after grace).Lab
Lab: host=h201 lane=headless remote lane command="uv run pytest tests/unit/event_bus/test_omn20464_orphan_degraded_grace.py tests/unit/event_bus/test_omn19355_dispatch_deadline.py tests/unit/services/test_runtime_health_dispatch_deadline_dimension_omn19355.py -q" head=250d3bb533 (branch OMN-20464-stuck-dispatch-degraded-grace) observed=19 passed
AC4 Status
NOT done. No dev-lane runtime-effects readback was performed. The docker shim on this host refuses mutating compose operations on the declared dev lane project, and no sanctioned runtime warm-redeploy path was available to this headless lane (the hotpatch-pr skill patches the plugin cache, not the runtime). Stopped instead of improvising. AC4 remains open.
Evidence-Ticket: OMN-20464
Evidence-Source: OCC#12687