Repository navigation
feat(OMN-16778): a stall nobody is told about is still an outage - #2178
Conversation
Phase 1 of epic OMN-16776, on top of the OMN-16777 consumer-flow projection. OMN-16777 made a stalled consumer visible. A projection nobody reads is not observability: OMN-14440 fired every 30 minutes for three months into a log nobody opened, and on 2026-08-27 the delegation chain died, quarantined 16 commands in about a minute, and nothing fired at all. This node is the half that speaks. node_consumer_flow_stall_alert_effect reads the flow projection and publishes onex.cmd.omnimarket.slack-publish.v1 when one (consumer_group, topic) holds STALLED or STARVED for the contract-declared number of consecutive windows. Delivery rides the canonical node_slack_publish_effect (OMN-13733) -- chat.postMessage with a bot token resolved from a contract secret ref. SLACK_WEBHOOK_URL is deliberately unused: HTTP 404 / no_service on both hosts, retiring under OMN-15600. Thresholds are contract data, and there is no code default to fall back on. The loader fails closed on a missing alert_policy block, and an AST walk over every .py file in the node asserts no threshold literal survives there. UNKNOWN never fires and never clears: it breaks an alerting run rather than extending it, and surfaces as its own WARN outcome. Confirm-2 / clear-8 hysteresis is the damping already proven on the .201 host reporter (OMN-16789); renotify is enforced by bucketing the idempotency key, so the publish node's existing ledger does the dedup and this node keeps no state file. EnumConsumerFlowState is promoted to omnimarket.models: two nodes now share it, and this repo's CLAUDE.md forbids one node importing another's model package. The Slack command is a documented wire mirror for the same reason -- the two nodes meet on a topic, not on an import edge -- with a round-trip test through the real ModelSlackPublish so the shapes cannot drift apart in silence.
|
Warning Review limit reachedNext included review available in 11 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 131 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (21)
Comment |
✅ Architectural Review — PASSEDErrors: 0 What this checks
No architectural violations found. Static architectural lint — no model inference (OMN-14176). |
✅ Hostile Reviewer — PASSEDBlocking findings (critical/error): 0 Gate semantics
Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524) |
…imarket#2178 (#7371) * evidence(OMN-16778): author OCC companion for OmniNode-ai/omnimarket#2178 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 5b847a95176ce748f1f19638f7e1196664848192. * evidence(OMN-16778): self-bind OCC#7371 + rebind contract_sha256 --------- Co-authored-by: omnimarket-bot <bot@omninode.ai>
OMN-16778 — a projection nobody reads is not observability
Phase 1 of epic OMN-16776, the other half of OMN-16777 (merged to
devas59885562). Execution gate OPEN (operator directive 2026-08-27).OMN-16777 made a stalled consumer visible. Visibility is not notification. OMN-14440 is the precedent this repeats if the projection ships alone: a job that fired every 30 minutes for three months, failed every time, and wrote 348KB of errors nobody read. And on 2026-08-27 the delegation chain died, quarantined 16 commands in about a minute, and nothing fired — the outage was found by hand, hop by hop, by someone running an unrelated verification.
node_consumer_flow_stall_alert_effectis the half that speaks.What it does
Consumes
onex.evt.omnimarket.projection-consumer-flow-applied.v1— the flow projection's own declared terminal event, no new transport — and publishesonex.cmd.omnimarket.slack-publish.v1when one(consumer_group, topic)holdsSTALLEDorSTARVEDfor the contract-declared number of consecutive windows.FAIL_CONFIRMED_STALLPENDING_CONFIRMATIONdeliver_warningsWARN_MISSED_WINDOWdeliver_warningsRECOVERINGNO_ALERTFour decisions that carry the weight
Thresholds are contract data, and there is no code default to fall back on.
ModelStallAlertPolicydeclares no field defaults;load_stall_alert_policyraisesStallAlertPolicyErroron a missing or malformedalert_policyblock rather than substituting a constant. AC2 is falsified by any threshold literal in a.pyfile, sotest_no_threshold_literal_lives_in_pythonwalks the AST of every Python file in the node and fails on a bare int/bool assigned to any threshold name — in an assignment, an annotated assignment, or a keyword argument. Grep would have missed a default buried in a signature.UNKNOWNneither fires nor clears. It breaks an alerting run rather than extending it. Counting an unobserved window as a continuation would let a runtime that stopped heartbeating manufacture a confirmed alert out of nothing — the mirror image of the false-green OMN-16777 closed. It surfaces instead as its ownWARN_MISSED_WINDOWoutcome, so it is neither silently healthy nor silently stalled (AC5). The observation model keeps the countersint | None; nothing here coerces an unobserved window to0.Asymmetric hysteresis, asserted rather than assumed.
confirm_windows: 2before firing,clear_windows: 8before calling it recovered, with a test that fails if the second is ever set at or below the first. This is the damping already proven for the.201host reporter under OMN-16789 — before it had that damping it produced the alert flap the operator asked to have stopped. An alert channel that flaps is muted within a day, at which point this node is worth exactly as much as the silence it replaced.No state file, and no second dedupe mechanism. The renotify period is enforced by bucketing the idempotency key on
window_start // renotify_after_seconds, sonode_slack_publish_effect's existing durable ledger collapses a repeat inside the 6h bucket. A standing stall re-posts at most once per period, and this node remembers nothing between invocations. The consecutive-window count and the recovery state are both derived from the window history itself, so there is no per-key state to go stale.Delivery is proven, not asserted
Delivery rides the canonical
node_slack_publish_effect(OMN-13733 consolidated every alert caller onto it) — Slack Web APIchat.postMessage, bot token resolved throughcontract_secret_ref.SLACK_WEBHOOK_URLis deliberately unused: re-probed 2026-08-27 on both~/.omnibase/.envand.201:/data/omninode/omnibase_infra/.env, it returnsHTTP 404 / no_service, and it is being retired under OMN-15600.The chain was driven end to end once, in-session, against the live channel — the real stall handler decided,
build_slack_commandrendered, andHandlerSlackPublishEffectresolved the secret and POSTed:One message, clearly labelled
:test_tube: OMN-16778 DELIVERY PROOF — synthetic, ignore., with the synthetic consumer group named as such in the payload. Per the ticket's own closeout note the assertion is on.ok, not on HTTP 200 — Slack returns 200 forchannel_not_foundandnot_in_channel. The proof script was a scratch file outside the repo; nothing underscripts/**is added by this diff.RED before GREEN
With the decision collapsed to the pre-change behaviour — fire on any single alerting window, and treat
UNKNOWNas a continuation of the run:Restored: 22 passed.
Two boundary decisions, both from this repo's own rules
CLAUDE.md: do not make one node import another node's private handler or model package.EnumConsumerFlowStateis promoted toomnimarket.models— two nodes share it now, and duplicating a verdict enum is precisely the drift OMN-16777's own writer comment warns about ("the SQL writer and the in-memory writer cannot drift into disagreeing about what STALLED means"). Moved, not shimmed.ModelStallAlertSlackCommand), not an import. These two nodes meet on a topic, not on a call edge, so the wire shape is the contract. It isextra="forbid", andtest_slack_command_is_accepted_by_the_publish_noderound-trips the payload through the realModelSlackPublishso the two shapes cannot drift apart in silence — the OMN-14490/OMN-14506 failure mode where every undeclared field was dropped without a word.Gates
state-coverage-gate(strict): both declared outputs are now pinned by name in the golden-chain test.PASS: 2 WARN: 0 FAIL: 0.test_market_node_runtime_dogfood:node_dirs388 → 389,entry_points385 → 386, each with its reason recorded beside the number.consumer_flow_windowsrecorded this node as a reader of a table it never touches. The docstring was reworded (with the reason written down) rather than the generated evidence being edited — this handler is pure and takes its history as input.dod_evidence
tests/test_omn16778_stall_alert_decision.py— 11 cases: confirmed stall fires (AC1), starved fires, single window silent, idle silent (AC3), flowing silent, UNKNOWN warns (AC5), UNKNOWN breaks a run, recovering silent, payload completeness (AC4), one idempotency key across the renotify window, two legs judged independently.tests/test_omn16778_stall_alert_thresholds_from_contract.py— 5 cases incl. the AST literal sweep (AC2) and fail-closed on a missingalert_policy.tests/test_golden_chain_consumer_flow_stall_alert.py— 6 cases walking every hop, incl. theModelSlackPublishround-trip and an assertion that the publisher still posts viachat.postMessagewithSLACK_BOT_TOKENand noSLACK_WEBHOOK_URL.uv run mypy src/omnimarket/nodes/node_consumer_flow_stall_alert_effect src/omnimarket/models/enum_consumer_flow_state.py --strict→Success: no issues found in 12 source files.uv run ruff check src/ tests/→All checks passed!pre-commitfull pass at commit time: all gates Passed (state-coverage-gate, projection contract-access gate OMN-16690, golden-chain coverage, dependency health, contract topic graph, unimported-handler check).Evidence-Ticket: OMN-16778
Evidence-Source: OCC#7371