Skip to content

test(OMN-16813): gate the in-process intent-execution arm — the leg a dead-chain report misread - #2952

Merged
github-actions[bot] merged 2 commits into
devfrom
jonah/omn-16813-intent-arm-chain-gate
Aug 28, 2026
Merged

github-actions[bot] merged 2 commits into
devfrom
jonah/omn-16813-intent-arm-chain-gate

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

OMN-16813 — gate the in-process intent-execution arm

Why one ticket id is described rather than named below. validator_occ_merge_eligibility extracts ticket_ids from this PR's title and body and requires an OCC contract for every id it finds. This PR cites a second ticket as the source of the finding, not as work it closes — its ACs are about a gateway link-health bus hop and its premise was falsified on 2026-08-27T20:21Z. Binding it here would have made the evidence sweep eligible to autoclose a ticket whose real work is untouched, so its contract was deliberately dropped from OCC companion #7353. Naming it in this body re-binds it and re-breaks the gate. It is named in full in this PR's commit message, in the test module docstring, and in #7353. The omission is a gate constraint, not evasion.

The Event Chain Gate (OMN-16774) asserts exactly two things: a terminal event lands, and nothing reaches the quarantine sink (test_event_chain_gate.py:562-595). Both are statements about DispatchResultApplier's Phase 2. Neither says anything about Phase 1 — intent execution — or about the IntentExecutor behind it.

That hole has already cost a day. Re-probed live on the .201 dev lane 2026-08-27, the gateway link-health chain — recorded as a dead chain by a High-priority ticket (see the note above), a status document, and the platform testing inventory (docs/tracking/2026-08-27-event-chain-testing-inventory.md, verdict row 7: "the canonical wired-but-never-fires shape") — delivers its 17,717 upserts entirely in-process:

handler -> DispatchResultApplier -> IntentExecutor -> intent effect

never over the bus. The chain was alive; every observer was blind, and blind in the same direction, because the only leg any of them watches is the one this chain does not use.

Why this cannot be a row in CHAIN_CASES

handler_wiring._normalize_handler_result routes a bare BaseModel return to output events; only ModelHandlerOutput.intents (or a ModelIntent in .result) reaches output_intents. Every existing gate row returns a bare typed model, so no number of appended rows reaches this leg. It needs an assertion aimed at the effect side of the seam.

What is real

EventBusInmemory, raw JSON wire bytes, EventBusSubcontractWiring's real consumer + deserializer, _prepare_handler_wiring arm selection, MessageDispatchEngine, the handler, DispatchResultApplier.apply, IntentExecutor.execute_all, and a real ProtocolIntentEffect. The single patched seam is _import_handler_class, exactly as the OMN-16774 gate patches it. Zero infrastructure: no broker, no database, no container, no lane.

Negative control — and what it actually measured

test_a_dead_intent_leg_is_invisible_on_every_topic_the_gate_watches runs the identical chain twice, differing only in whether the executor carries a handler for the intent_type. Measured, not assumed:

Sink live run dead run
terminal topic EMPTY EMPTY
quarantine sink (the only one OMN-16774 watches) EMPTY EMPTY
boundary DLQ (get_dlq_topic_for_original, resolved not hardcoded) EMPTY EMPTY
intent effect calls 1 0

The refusal is raised — IntentExecutor names the unroutable intent_type — but EventBusInmemory exposes no _publish_raw_to_dlq and a RuntimeHostError is classified retryable, not exhausted, on first delivery, so the exception is logged as Subscriber callback failed and never reaches the publisher.

Correction recorded rather than hidden: the first draft of this suite asserted pytest.raises around bus.publish and was wrong for exactly that reason. The assertions were rewritten against what the transport actually does. This is why the negative control exists — it caught my own bad assumption before review did.

So on this leg, delivered work and bus-observable state are not the same measurement, and the effect_calls assertion is the only one in this repo that can go red when the intent arm breaks.

RED-first proof (run and captured, not claimed)

register_effect default flipped to False, positive test re-run:

E   AssertionError: the intent effect was called 0 time(s), expected exactly 1.
    Zero calls is the shape the live outage had: the chain consumed the heartbeat,
    committed, and delivered nothing — with no terminal and no quarantine
    record to show for it.
1 failed in 0.28s

Reverted → 4 passed. The break is committed as an assertion, never as a broken chain row.

Two further refusals pinned

  • an unregistered intent_type, and
  • an applier built with no executor, which must refuse in Phase 1 rather than fall through and publish a terminal advertising a write that never happened — a dead leg that reports success.

AC3 — the one branch that does not refuse

IntentExecutor.execute answers a None payload with a WARNING and a bare return. That branch is unreachable from validated data (ModelIntent.payload is required; pydantic rejects None), and that refusal is now asserted rather than assumed, so the guarantee cannot be relaxed by a model edit without going red.

Enforcement, not detection (rule 5)

No workflow edit. The Event Chain Gate job already runs uv run pytest tests/integration/chains/ wholesale and is registered in scripts/ci/ci_summary_gate.py::STRICT_GATE_JOBS, so a skipped/absent conclusion fails CI Summary closed. CI Summary is dev's only required check. This file is collected the moment it lands.

Non-goals

No runtime behavior change. The DLQ/quarantine router is owned by OMN-16798 (#2949, #2951, both landed 2026-08-27) and the inventory's §7.2 PermissionError-swallow finding — deliberately not touched here to avoid colliding with a concurrent lane.

Residual observed while pushing

The governed pre-push selector reported deferred to CI (integration needs live services; this hook is unit-scoped): [tests/integration/chains/]. This suite needs no live service — the selector's directory heuristic treats all of tests/integration/ as service-dependent. Not a blocker (the dedicated CI job runs it), but it is the same "the check exists but does not observe this" shape this PR is about. Recorded, not fixed here.

Acceptance criteria

  • AC1 — chain driven wire-bytes → effect, asserting payload identity and correlation-id propagation: MET
  • AC2 — non-vacuous negative control, committed as a passing assertion: MET (RED proof above)
  • AC3 — the silent-drop branch pinned as a recorded decision: MET
  • AC4 — inside the already-required gate, no workflow edit, strict-registered: MET
  • AC5 — zero infrastructure: MET

Evidence: uv run pytest tests/integration/chains/ -q → 8 passed

Evidence-Ticket: OMN-16813
Evidence-Source: OCC#7363

…MN-16755 read as dead

The OMN-16774 Event Chain Gate asserts exactly two things: a terminal event
lands, and nothing reaches the quarantine sink. Both are statements about
DispatchResultApplier's Phase 2 (output-event publish). Neither says anything
about Phase 1 -- intent execution -- or about the IntentExecutor behind it.

That hole has already cost a day. Re-probed live on the .201 dev lane
2026-08-27, the gateway link-health chain -- recorded as a DEAD CHAIN by a
High-priority ticket (OMN-16755), a status doc, and the platform testing
inventory (verdict row 7, "the canonical wired-but-never-fires shape") --
delivers its 17,717 upserts entirely in-process:

    handler -> DispatchResultApplier -> IntentExecutor -> intent effect

never over the bus. The chain was alive; every observer was blind, and blind in
the same direction, because the only leg any of them watches is the one this
chain does not use.

Why this cannot be a row in CHAIN_CASES
---------------------------------------
handler_wiring._normalize_handler_result routes a bare BaseModel return to
output_EVENTS; only ModelHandlerOutput.intents (or a ModelIntent in .result)
reaches output_intents. Every existing gate row returns a bare typed model, so
no number of appended rows reaches this leg. It needs an assertion aimed at the
effect side of the seam, which is what this module adds.

Every hop is production: EventBusInmemory, raw JSON wire bytes,
EventBusSubcontractWiring's real consumer + deserializer, _prepare_handler_wiring
arm selection, MessageDispatchEngine, the handler, DispatchResultApplier.apply,
IntentExecutor.execute_all, and a real ProtocolIntentEffect. The single patched
seam is _import_handler_class, exactly as the OMN-16774 gate patches it. Zero
infrastructure: no broker, no database, no container, no lane.

Negative control, and what it measured
--------------------------------------
test_a_dead_intent_leg_is_invisible_on_every_topic_the_gate_watches runs the
identical chain twice, differing only in whether the executor carries a handler
for the intent_type. Measured, not assumed:

  - terminal topic ............. EMPTY in both runs
  - quarantine sink ............ EMPTY in both runs (the ONLY sink OMN-16774 watches)
  - boundary DLQ ............... EMPTY in both runs

The refusal IS raised -- IntentExecutor names the unroutable intent_type -- but
EventBusInmemory exposes no _publish_raw_to_dlq and a RuntimeHostError is
classified retryable, not exhausted, on first delivery, so the exception is
logged as "Subscriber callback failed" and never reaches the publisher. The
initial draft of this suite asserted pytest.raises around bus.publish and was
WRONG for exactly that reason; the assertions were rewritten against what the
transport actually does rather than what it was assumed to do.

So on this leg, delivered work and bus-observable state are not the same
measurement, and the effect_calls assertion is the only one in this repo that
can go red when the intent arm breaks. That is what makes it non-vacuous.

RED-first proof (not a claim -- run and captured):
  register_effect default flipped to False, positive test re-run ->
  E  AssertionError: the intent effect was called 0 time(s), expected exactly 1.
  1 failed. Reverted; 4 passed.

Two further refusals are pinned so they cannot regress into silent commits: an
unregistered intent_type, and an applier built with no executor at all (which
must refuse in Phase 1 rather than fall through and publish a terminal event
advertising a write that never happened -- a dead leg that reports success).

AC3: IntentExecutor.execute answers a None payload with a WARNING and a bare
return, no raise. That branch is UNREACHABLE from validated data --
ModelIntent.payload is required and pydantic rejects None -- and that refusal is
now asserted rather than assumed, so the guarantee cannot be relaxed by a model
edit without going red. The branch stays as defence-in-depth.

Enforcement, not detection (rule 5): no workflow edit. The Event Chain Gate job
already runs `uv run pytest tests/integration/chains/` wholesale and is
registered in ci_summary_gate.py::STRICT_GATE_JOBS, so a skipped or absent
conclusion fails CI Summary closed. This file is collected the moment it lands.

NO runtime behavior change. The DLQ/quarantine router is owned by OMN-16798
(#2949, #2951, both landed 2026-08-27) and the inventory's section 7.2
PermissionError-swallow finding; deliberately not touched here.

Evidence: uv run pytest tests/integration/chains/ -q -> 8 passed
Evidence-Ticket: OMN-16813
@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 58 minutes.

View limit details

Limit 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.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8fde25c-bc03-4647-bf8b-58d6c7c54e32

📥 Commits

Reviewing files that changed from the base of the PR and between 4529c34 and fe31f58.

📒 Files selected for processing (1)
  • tests/integration/chains/test_intent_arm_chain_gate.py

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Blocking findings (critical): 0
Total findings: 0
Models succeeded: none

Note: All reviewer models failed or were unavailable. Degraded results are informational during the pilot phase (OMN-8468/OMN-8524) and do not block merge. Error: all review endpoints [192.168.86.201:8000 192.168.86.201:8000 ] unreachable — preflight short-circuit (no models available)


Gate semantics (pilot phase)

Verdict Meaning Blocks merge?
passed No critical findings No
blocked CRITICAL findings found Yes
degraded All models unavailable (infra) No (pilot)

Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review (OMN-8468/OMN-8524)

jonahgabriel added a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 28, 2026
#7353)

* evidence: OCC companion pass 1 for OmniNode-ai/omnibase_infra#2952

* evidence: OCC companion self-bind for #7353

* evidence(OMN-16813): behavior-proving check, and drop the incorrect OMN-16755 binding

Two corrections to the autobind output on this companion.

1. DROPPED contracts/OMN-16755.yaml and its three receipts.

The autobind bound OMN-16755 because that ticket id appears in omnibase_infra#2952's
prose -- the PR cites it as the SOURCE of the finding, not as work it closes.
OMN-16755's own ACs are about a gateway link-health bus hop, and its premise was
falsified on 2026-08-27T20:21Z (the chain routes in-process and is alive). #2952
satisfies none of them.

Left in place, that contract is a live false-autoclose hazard: the
evidence-autoclose sweep would have read three passing surrogates against
OMN-16755 and had grounds to flip a ticket whose real work is untouched. Removing
the binding is the honest state; OMN-16755 keeps its own separate disposition.

2. ADDED a BEHAVIOR-PROVING evidence item to OMN-16813.

The three autobind items are all surrogates and would leave
behavior_proving_count = 0:

  - two BYTE-IDENTICAL file-content greps (static inspection, and they grep a
    class NAME rather than any assertion, so they pass on a file whose tests were
    all deleted),
  - one foreign suite (tests/test_evidence_admissibility.py, this repo's own),
  - one bare PR-state read.

None executes the chain the ticket is about. The new item runs the actual suite --
the same command the required `Event Chain Gate` CI job runs.

RECEIPT IS ADVERSARIAL, runner != verifier (OMN-12791). My first attempt set both
to this lane and the receipt-honesty gate correctly rejected it as
SELF_ATTESTATION. The independent verifier is GitHub Actions' own required
`Event Chain Gate` job 98727318636 (run 33133247111, conclusion=success) on
omnibase_infra#2952, whose log is reproduced verbatim in probe_stdout:

  test_intent_arm_reaches_the_real_effect_handler .................... PASSED
  test_a_dead_intent_leg_is_invisible_on_every_topic_the_gate_watches  PASSED
  test_missing_intent_executor_refuses_rather_than_publishing_a_terminal PASSED
  test_validation_is_what_keeps_the_executors_silent_drop_unreachable  PASSED
  8 passed in 1.22s

check_value carries a `repos/OmniNode-ai/omnibase_infra/tree/<sha>` reference
because commit_sha is an omnibase_infra commit and COMMIT_SHA_EXISTS cannot
resolve it in this repo -- also caught by the gate on the first attempt, not
assumed.

Existing receipts are unaffected: contract_entry_sha256 is per-entry (OMN-13888)
and authoritative when present, so appending an item does not unbind them.

Verified: check_receipt_hardening.py exit 0; pre-commit clean on both files.

Evidence-Ticket: OMN-16813

* evidence(OMN-16813): name the receipt for its check_type so the eligibility gate resolves it

The receipt id validator_occ_merge_eligibility looks for is
<ticket>:<evidence_item_id>:<check_type>, and it resolves the file by check_type
name -- command.yaml for check_type: command, test_passes.yaml for
check_type: test_passes. The autobind only ever emits command receipts, so the
new behavior-proving item (check_type: test_passes) was written to command.yaml
by pattern-matching its siblings, and the gate correctly reported:

  missing_or_nonpass_receipts:
    ["OMN-16813:dod-behavior-intent-arm-chain-gate:test_passes"]

Renamed, not re-typed. Downgrading the check to check_type: command to match the
filename would have made the gate pass while turning the only behavior-proving
leg back into a surrogate -- which is the whole defect this item exists to fix.

Content unchanged; check_receipt_hardening.py still exits 0 (contract_entry_sha256
binds the entry, not the path).

Evidence-Ticket: OMN-16813

---------

Co-authored-by: node-occ-companion-effect <occ-companion-effect@omninode.ai>
Co-authored-by: jonahgabriel <jonah@omninode.ai>
@jonahgabriel jonahgabriel changed the title test(OMN-16813): gate the in-process intent-execution arm — the leg OMN-16755 read as dead test(OMN-16813): gate the in-process intent-execution arm — the leg a dead-chain report misread Aug 28, 2026
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 28, 2026
…ibase_infra#2952 (#7363)

* evidence(OMN-16813): author OCC companion for OmniNode-ai/omnibase_infra#2952

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head fe31f58a9d60f62432f65aa5373e45a1d88098b7.

* evidence(OMN-16813): self-bind OCC#7363 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
@github-actions
github-actions Bot merged commit eb05eee into dev Aug 28, 2026
179 of 186 checks passed
@github-actions
github-actions Bot deleted the jonah/omn-16813-intent-arm-chain-gate branch August 28, 2026 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant