Repository navigation
fix(OMN-20590): the lane's main runtime publishes the runtime tick, as an envelope the consumers accept - #4575
Conversation
…s an envelope the consumers accept RuntimeScheduler (OMN-953) had no instantiation site, so nothing published onex.intent.platform.runtime-tick.v1 on any lane, and its emit_tick published a bare ModelRuntimeTick that every auto-wired consumer refuses. The scheduler now publishes a ModelEventEnvelope with event_type platform.runtime-tick through publish_envelope; the kernel starts one in the main profile when the lane overlay sets ONEX_RUNTIME_SCHEDULER_ENABLED, and warns at boot when it does not. dev-202 is the one lane that opts in. Onex-Lane: runtime-tick-9143 Onex-Session: 4e719e884f5d4b20a09ca235eed9662e
…the ticks now reach Both prune effects resolve their local archive directory on every tick. On dev-202 neither name was set, so each tick raised KeyError and was dead-lettered. The directories live on the role's own data volume. Onex-Lane: runtime-tick-9143 Onex-Session: 4e719e884f5d4b20a09ca235eed9662e
…andler end to end Adds the integration chain start_lane_runtime_scheduler -> emit_tick -> bus -> auto-wired runtime-tick subscription -> HandlerGitHubApiPoll. Corrects the envelope rationale: a bare tick is refused by the subcontract-wiring path and wrapped with a lineage-less minted id at the auto-wiring boundary; it is not refused by every consumer. Onex-Lane: runtime-tick-9143 Onex-Session: 4e719e884f5d4b20a09ca235eed9662e
|
Lab: host=h202 lane=dev-202 (compose project omnibase-infra-dev-202) head=f2eb0ca9c command=hotpatch of omninode-dev-202-runtime with this branch's runtime_scheduler.py, runtime_profile.py, service_kernel.py plus ONEX_RUNTIME_SCHEDULER_ENABLED=true exported in the entrypoint, tick topic pointed at the scratch topic onex.intent.platform.runtime-tick-proof-omn20590.v1, then rpk topic describe twice 10 s apart and an in-container decode. observed=kernel log 09:55:36Z 'Runtime tick producer started: scheduler_id=dev-202-omn20590-proof ... interval_ms=1000'; scratch HWM 14 -> 23 in 10 s; records decode as ModelEventEnvelope with event_type platform.runtime-tick and a ModelRuntimeTick payload, seq 21-23 monotonic; effects and worker roles started no producer. Restored at 09:56:29Z (md5s match the image, healthy after a 324.070 s boot, no producer started, scratch topic deleted). The later head adds only the integration test and comment and docstring corrections, no runtime code change. |
…12894) * evidence(OMN-20590): author OCC companion for OmniNode-ai/omnibase_infra#4575 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head f2eb0ca9c141158d43fb344f34dcf3e64854a49a. * evidence(OMN-20590): self-bind OCC#12894 + 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: 2
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 14
Nit-level findings suppressed: 1
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: 14 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/runtime/runtime_profile.py(gpt-oss-review) — Missing import for os in runtime_profile | The new function resolve_runtime_scheduler_enabled uses os.environ but the module does not import the os package. Invoking the function will raise a NameErro - [CRITICAL]
tests/unit/runtime/test_runtime_scheduler.py(gpt-oss-review) — Unit test mock missing publish attribute | tests/unit/runtime/test_runtime_scheduler.py defines mock_event_bus with only a publish_envelope AsyncMock. The test test_tick_published_as_envelope_the_cons - [MAJOR]
docker/docker-compose.dev-202.yml(qwen3-review) — Docker compose exposes port on 172.17.0.1 (Docker bridge gateway) | The diff adds ports 172.17.0.1:61085:8085 and 172.17.0.1:61086:8085 to the omninode-runtime and runtime-effects services. Binding to - [MAJOR]
tests/unit/runtime/test_runtime_scheduler_wiring.py(qwen3-review) — No test for the case where a non-main role has an invalid flag value | The test suite covers the case where a main role with an invalid flag value raises ProtocolConfigurationError, but does not test - [MAJOR]
tests/unit/runtime/test_runtime_scheduler.py(gpt-oss-review) — Incorrect assertion on publish method in envelope test | The same test also checks that no publish call occurred via mock_event_bus.publish.assert_not_called(), but the scheduler now uses publish_enve - [MAJOR]
tests/integration/runtime/test_runtime_tick_producer_golden_chain_omn20590.py(gpt-oss-review) — Integration test does not clean up scheduler on exception | tests/integration/runtime/test_runtime_tick_producer_golden_chain_omn20590.py creates a scheduler and calls await scheduler.stop() in a fina - [MINOR]
src/omnibase_infra/runtime/runtime_profile.py(qwen3-review) — resolve_runtime_scheduler_enabled reads env var on every call | The function reads os.environ.get() on every invocation. In the current code, it is called once per boot in start_lane_runtime_scheduler - [MINOR]
tests/unit/runtime/test_runtime_scheduler_wiring.py(qwen3-review) — Test uses SUFFIX_RUNTIME_TICK but scheduler config uses tick_topic | The test test_main_profile_with_flag_starts_and_publishes_envelope asserts that the topic equals SUFFIX_RUNTIME_TICK, but the sched - [MINOR]
tests/integration/runtime/test_runtime_tick_producer_golden_chain_omn20590.py(qwen3-review) — Integration test does not verify that the scheduler loop is actually running | The integration test test_lane_producer_tick_drives_a_tick_subscriber calls scheduler.emit_tick() manually and then stops - [MINOR]
src/omnibase_infra/runtime/runtime_scheduler.py(qwen3-review) — TICK_PRODUCER_PROFILE is a magic string 'main' | The constant TICK_PRODUCER_PROFILE is set to 'main', which is a string literal that must match the value returned by resolve_runtime_profile_name(). If - [MINOR]
src/omnibase_infra/runtime/runtime_scheduler.py(qwen3-review) — No test for the case where the event bus is not started | The start_lane_runtime_scheduler function assumes that the event bus is already started. If the bus is not started, the scheduler's start() ma - [MINOR]
src/omnibase_infra/runtime/runtime_scheduler.py(gpt-oss-review) — Event type constant may not match contract expectations | RUNTIME_TICK_EVENT_TYPE is set to "platform.runtime-tick" while existing contracts and documentation reference "runtime.tick.v1" as the event - [MINOR]
tests/unit/runtime/test_runtime_scheduler_wiring.py(gpt-oss-review) — Unnecessary JSON round‑trip in envelope validation test | tests/unit/runtime/test_runtime_scheduler_wiring.py serialises and deserialises the envelope via json.dumps/json.loads to validate shape. This - [MINOR]
src/omnibase_infra/runtime/runtime_scheduler.py(gpt-oss-review) — Transport type hard‑coded to KAFKA in error context | RuntimeScheduler.emit_tick constructs ModelInfraErrorContext with transport_type=EnumInfraTransportType.KAFKA regardless of the actual event bus i
Findings demoted from threads (anchor rejected)
-
[MAJOR] hostile-reviewer (qwen3-review)
start_lane_runtime_scheduler raises ProtocolConfigurationError for non-main roles | The function calls resolve_runtime_scheduler_enabled() before checking the runtime_profile. If a non-main role (e.g., 'effects', 'workers') boots on a lane where ONEX_RUNTIME_SCHEDULER_ENABLED is set to an invalid value like 'ture', the function will raise ProtocolConfigurationError instead of returning None. This means a typo in the env var on a lane will crash non-main roles that should be unaffected by the tick producer s
Resolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[MAJOR] hostile-reviewer (gpt-oss-review)
Potential mismatch of ModelEventEnvelope field name | RuntimeScheduler.emit_tick constructs ModelEventEnvelope using the keyword argument source_tool, but the typical schema for ModelEventEnvelope uses a field named source. If the model does not define source_tool, instantiation will fail at runtime, preventing tick emission. | Evidence: runtime_scheduler.py: envelope = ModelEventEnvelope(..., source_tool=f"runtime-scheduler.{self.scheduler_id}", ...) | Fix: Verify the ModelEventEnvelope definition; if the
Resolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492).
✅ Hostile Reviewer — REVIEWEDCritical findings: 2
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) |
|
Hostile-review disposition (review 5413249388). Both threaded findings were demoted into the review body ("anchor rejected"), so no thread exists to reply under; every finding is answered here. This PR merged at f2eb0ca before the answer; the fix is in omnibase_infra#4579. The per-lane opt-in is unchanged there: dev-202 is still the only overlay that sets the switch. Demoted from threads
Below quorum
|
Resolves the docker-compose.dev-202.yml conflict with #4575 (OMN-20590): runtime-effects keeps both dev's archive-dir variables and this branch's blank OMNIMARKET_PROJECTION_READ_BINDING_OVERLAY. Onex-Lane: omn20159-read-binding Onex-Session: e72e9157-677b-4b13-bd77-daa2be198c16
Closes OMN-20590
Summary
This pull request starts the runtime tick producer that seven existing contract subscribers wait on, which no runtime started until now. It also publishes each tick as an envelope with the event_type those contracts route on, so the tick carries its own lineage. The implementation introduces an opt-in mechanism controlled by the
ONEX_RUNTIME_SCHEDULER_ENABLEDenvironment variable. Only themainrole is permitted to publish ticks to prevent duplicate emissions in shared environments. The change is validated through unit tests and a controlled lab proof on the dev-202 lane.Symptom (read live 2026-10-05)
Seven contracts subscribe
onex.intent.platform.runtime-tick.v1and nothing published it. HIGH-WATERMARK / LOG-START on the tick topic: .201 dev lane 0 / 0, .201 stability-test 0 / 0, k3s onex-lab 0 / 0 (positive control,onex.evt.platform.node-heartbeat.v1on the same brokers: 592547, 278271, 230843). dev-202 held 126 records, every one a synthetic tick hand-published by another lane (two tick ids, 63 copies each).Cause
RuntimeScheduler(src/omnibase_infra/runtime/runtime_scheduler.py, OMN-953) had no instantiation site insrc.service_kernel.pyonly startsContractRegistrationEventRouter, whose tick is in-process (metadata topic__internal_tick__) and never reaches the bus. No compose, onex-lab, or onex-dev overlay sets anyONEX_RUNTIME_SCHEDULER_variable, meaning there was no switch to turn on.emit_tickpublished a bareModelRuntimeTickthroughpublish()with headerevent_typeruntime.tick.v1, while every subscribing contract routes onevent_typeplatform.runtime-tick. The two consume paths treat a bare tick differently:EventBusSubcontractWiring._deserialize_to_enveloperefuses it outright (ModelEventEnvelope.model_validatefails on the missingpayloadkey, checked 2026-10-05), and the auto-wiring consume boundary, which the seven tick subscribers use, wraps it in a synthesized envelope whose minted id carries no lineage (OMN-18958). A scratch run of the integration chain below with a bare tick did reach the poller handler, so part 2 is a shape and lineage defect, not the reason nothing fired. Part 1 alone is why no lane ever saw a tick.Change
src/omnibase_infra/runtime/runtime_scheduler.py:emit_ticknow publishesModelEventEnvelope[ModelRuntimeTick]withevent_typeplatform.runtime-tick(constantRUNTIME_TICK_EVENT_TYPE),source_toolruntime-scheduler.<scheduler_id>, viapublish_envelope(envelope=, topic=, key=scheduler_id). The constructor takesProtocolEventBusLikeinstead ofEventBusKafka. New functionstart_lane_runtime_scheduler(event_bus, runtime_profile)returnsNonefor any profile other thanmain; formain, it returnsNoneand logs aWARNINGnaming the tick topic and the switch whenONEX_RUNTIME_SCHEDULER_ENABLEDis not true; otherwise it starts and returns the scheduler.runtime_profile.py:resolve_runtime_scheduler_enabled()readsONEX_RUNTIME_SCHEDULER_ENABLEDat the existing lane-policy env boundary. Unset or blank is false;true/1/yes/onandfalse/0/no/offare accepted; anything else raisesProtocolConfigurationErrorinstead of reading as false.service_kernel.py: Step 9.6b starts the producer after the contract registry consumers; both shutdown paths call its idempotentstop().docker/docker-compose.dev-202.yml:omninode-runtimesetsONEX_RUNTIME_SCHEDULER_ENABLED"true";runtime-effectssetsONEX_DEAD_LETTER_ARCHIVE_DIR=/app/data/dead-letter-archiveandONEX_CONSUMER_FLOW_ARCHIVE_DIR=/app/data/consumer-flow-archive, because both prune effects resolve those names on every tick and on dev-202 each tick raisedKeyErrorONEX_CONSUMER_FLOW_ARCHIVE_DIRand was dead-lettered.Why opt-in per lane
Only the main role publishes, so roles sharing an env anchor never double ticks. The first ticks a lane receives activate every subscriber, including two prunes configured
dry_runfalse and the delegation reaper, so a lane changes only by an explicit overlay edit.Tests
tests/unit/runtime/test_runtime_scheduler_wiring.py: Validates switch parsing including refusal of unparseable values; main+switch starts and publishes an envelope; effects/workers/projection-api never start one; main without switch starts none and warns naming topic and switch; dev-202 overlay sets the switch on omninode-runtime and the two archive dirs on runtime-effects; the switch appears in docker-compose.dev-202.yml only; an envelope survives the consumer deserializer shape.tests/unit/runtime/test_runtime_scheduler.py: Moved frompublishtopublish_envelopeand gainedtest_tick_published_as_envelope_the_consumers_accept.test_omn19494_compose_runtime_lane73 passed with wiring.tests/integration/runtime/test_runtime_tick_producer_golden_chain_omn20590.py:start_lane_runtime_scheduler(main, switch on) ->emit_tick-> in-memory bus -> auto-wired runtime-tick subscription ->HandlerGitHubApiPoll-> result topic, 1 PR polled, no errors. Passes together withtest_github_api_poll_golden_chain.resolve_runtime_scheduler_enabled,start_lane_runtime_schedulerandRUNTIME_TICK_EVENT_TYPEdo not exist there.Lab proof (dev-202)
onex.intent.platform.runtime-tick-proof-omn20590.v1so a concurrent lane's replay-storm measurement on the real tick topic was not disturbed.ModelEventEnvelope[object].model_validatethenModelRuntimeTick.model_validate:event_typeplatform.runtime-tick,scheduler_iddev-202-omn20590-proof,seq21, 22, 23, monotonic.runtime-effectsandruntime-workerlogged no producer start.Not changed
test_overlay_flag_is_set_by_dev_202_alonepins that list, so the next lane is a reviewed diff.Landing order
Independent of other open PRs; the prune handler-side lazy archive read and DLQ replay dedup belong to OMN-20589, which this PR does not touch.
Evidence-Ticket: OMN-20590
Evidence-Source: OCC#12894