Skip to content

fix(OMN-18419): the delegation chain records its causal edges, and the replay grades the tree it declares - #3610

Merged
jonahgabriel merged 4 commits into
devfrom
jonah/omn-18419-chain-tree-replay
Sep 16, 2026
Merged

jonahgabriel merged 4 commits into
devfrom
jonah/omn-18419-chain-tree-replay

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes OMN-18419.

chain-canary link 5 reported ledger_replay_failed (4/5 links proven) on run
35037024216 (2026-09-16T00:06:25Z) for a chain that had run end to end. Two
independent defects produced that one verdict, and each is fixed and provable
on its own.

The evidence

Read read-only on the .201 compose dev lane, public.event_ledger, correlation
41235987-425c-481b-b2e3-8970083ce512 (that run's own):

onex.cmd.omnimarket.delegate-skill.v1                  6c9dc22f-…  parent=<null>   off 419   00:05:51.473
onex.cmd.omnibase-infra.delegation-routing-request.v1  841f9c10-…  parent=<null>   off 1081  00:05:54.498
onex.evt.omnibase-infra.routing-decision.v1            223b9623-…  parent=841f9c10-…  off 1052  00:05:54.586
onex.evt.omnimarket.delegate-skill-completed.v1        3b1b8925-…  parent=6c9dc22f-…  off 332   00:06:20.915

public.ledger_chain graded hops 1 and 3 replay_green = false, tier-2 pass
on all four.

1. The causal edge was dropped at two publish seams

The pattern-B worker command. RuntimePatternBBroker._publish_worker_command
builds a fresh envelope for the route's command topic and published it as a
chain HEAD every time. It publishes two hops of this chain: the head, from the
HTTP ingress, and onex.cmd.omnibase-infra.delegation-request.v1, from
service_delegation_dispatch_port — which runs inside the delegate-skill
handler's dispatch, with the consumed envelope already bound on the engine's
contextvar. It now records that envelope. Absence is preserved: the ingress hop
consumes nothing and still publishes with no parent, which is the checkable
statement "head".

The state_io in-row outbox. MessageDispatchEngine hands a dispatcher the
JSON-safe materialization of the envelope and binds the typed one beside it
"for transport identity (for example envelope_id)" — its own comment. The
stateful wrapper read envelope_id as an attribute off that mapping, which is
always None, so every outbox entry seeded its causation with the correlation
id via the fallback. That is provable from the wire rather than asserted: the
routing intent's deterministic id on this run re-derives exactly as
uuid5(correlation, f"{correlation}:ModelRoutingIntent:0"), and not from the
delegation-request envelope f1cc9a51-ef02-40f4-b2e7-81e5b57942ed read off
the dev-lane broker that actually caused it. _publish_outbox_batch then
dropped even that seed from the envelope it published. Both halves are fixed.
The completion-bound sweep's deliberate self-seed still publishes no edge,
because a correlation id is not an envelope id and recording one would fabricate
an edge that can never close.

2. The chain is a tree and the replay re-derived a line

chain_replay._replay_one_hop compared each hop's recorded parent against
observed[index - 1]. delegate-skill-completed records the delegate-skill
command as its parent — it is a consequence of consuming that command, not of
the routing decision that precedes it in time — so a correct edge graded red.

chain_topology is now an ordered list of hops that each declare their parent
topic (ModelDeclaredChainHop), and the replay grades the recorded edge against
the envelope observed on the declared parent. Nothing about this canary is
special-cased: a line is a tree whose every parent is the preceding hop, and it
grades identically under both readings. Tier 2 still reads the order; tier 1 now
reads the parent relation. Two consequences, both deliberate:

  • A hop no declaration reaches replays RED with a stated reason rather than
    falling back to the positional guess. A check that could not run has not
    passed — the rule tier 2 already held.
  • A declared head that records a parent now fails. Before this the head was
    green by construction, so that case went unreported.

The declaration also gains the delegation-request.v1 hop, which was always
traversed and never declared, and node_ledger_projection_compute (contract
1.4.0) records it. That node is the only writer of public.event_ledger, so
without it the routing request's recorded parent named an envelope no
correlation-scoped read could resolve. The canary's declared hop set moves to
the same five, in the same order.

Red-first

Against parent 76c8658f1, in a detached worktree at that commit:

tests/integration/runtime/test_chain_causal_edge_seam_omn18419.py
  3 failed, 1 passed
  test_worker_command_records_the_envelope_whose_dispatch_produced_it
    AssertionError: assert None == UUID('1735b2ed-…')
  the two engine-seam tests fail on ImportError (the helper does not exist there)
  test_worker_command_from_the_ingress_is_still_a_chain_head PASSES
    — the positive control: the head hop was already right and stays right

Replaying the four rows above through the parent's own assemble_replay_and_verify
reproduces the live verdict exactly:

0 True  pass
1 False pass  parent re-derives to 6c9dc22f-… from the preceding hop …delegate-skill.v1
2 True  pass
3 False pass  parent re-derives to 223b9623-… from the preceding hop …routing-decision.v1
ALL GREEN: False

At this head: tests/unit/nodes/node_delegation_chain_ledger_effect/test_chain_replay.py
15 passed, the integration seam file 4 passed. The post-fix five-hop wire shape
grades 5/5 green against the shipped contract, and the four rows as recorded
still grade red — the fix does not paper over the evidence that produced the
defect.

Each test carries a falsifier beside it: the same observed tree graded against a
line declaration fails the branch; a parent that is not the declared hop's
envelope still fails; the materialized payload really is the attribute-less
mapping that caused the fallback.

Verification

  • uv run mypy src/omnibase_infra/ — no issues in 3044 source files
  • pre-commit run --all-files — green (the topic enum regeneration is included)
  • Focused: the chain-ledger node suite (30), the canary node suite, the state_io
    outbox and completion-bound suites, the OMN-18389 broker seam file, the ledger
    projection contract and dispatch-reachability suites — all green
  • Lab first (rule 24): this repo's automatic compose-dev lab pass on merge is
    the lab proof; its receipt for the squash sha is quoted on the ticket. The
    live verdict is the next scheduled chain-canary run after the merge reaches
    the lane, which is the AC5 falsifier and is not dispatched by hand.

Evidence-Ticket: OMN-18419
Evidence-Source: OCC#9755

…e replay grades the tree it declares

chain-canary link 5 reported `ledger_replay_failed (4/5 links proven)` on a
chain that had run end to end. Two independent defects, one verdict.

1. The causal edge was dropped at two publish seams.

   `RuntimePatternBBroker._publish_worker_command` builds a fresh envelope for
   the route's command topic and published it as a chain HEAD every time. It
   publishes TWO hops of this chain: the head, from the HTTP ingress, and
   `onex.cmd.omnibase-infra.delegation-request.v1`, from
   `service_delegation_dispatch_port` -- which runs INSIDE the delegate-skill
   handler's dispatch, with the consumed envelope bound on the engine's
   contextvar. It now records that envelope. Absence is preserved: the ingress
   hop consumes nothing and still publishes with no parent, which is the
   checkable statement "head" the replay now checks rather than assumes.

   `MessageDispatchEngine` hands a dispatcher the JSON-safe materialization of
   the envelope and binds the typed one beside it "for transport identity (for
   example envelope_id)" -- its own words. The state_io wrapper read
   `envelope_id` as an ATTRIBUTE off that mapping, always None, so every outbox
   entry seeded its causation with the CORRELATION id via the fallback. Proven
   from the wire: the routing intent's deterministic id on run 35037024216
   re-derives as `uuid5(correlation, f"{correlation}:ModelRoutingIntent:0")`
   and not from the `delegation-request` envelope
   `f1cc9a51-ef02-40f4-b2e7-81e5b57942ed` that caused it. `_publish_outbox_batch`
   then dropped even that seed from the envelope it published. Both are fixed;
   the completion-bound sweep's deliberate self-seed still publishes no edge,
   because a correlation id is not an envelope id.

2. The chain is a TREE and the replay re-derived a LINE.

   `chain_replay._replay_one_hop` compared each hop's recorded parent against
   `observed[index - 1]`. `delegate-skill-completed` records the delegate-skill
   COMMAND as its parent -- it is a consequence of consuming that command, not
   of the routing decision that precedes it in time -- so a correct edge graded
   red. `chain_topology` is now an ordered list of hops that each declare their
   parent TOPIC (`ModelDeclaredChainHop`), and the replay grades the recorded
   edge against the envelope observed on the declared parent. Nothing about
   this canary is special-cased: a line is a tree whose every parent is the
   preceding hop. Tier 2 still reads the ORDER; tier 1 now reads the PARENT
   RELATION. A hop no declaration reaches replays RED with a stated reason
   rather than falling back to the positional guess -- the same rule tier 2
   already held.

The declaration also gains the `delegation-request.v1` hop, which was always
traversed and never declared, and `node_ledger_projection_compute` (contract
1.4.0) now records it -- that node is the only writer of `public.event_ledger`,
so without it the routing request's recorded parent named an envelope no
correlation-scoped read could resolve.

Evidence, read read-only on the .201 compose dev lane, correlation
41235987-425c-481b-b2e3-8970083ce512 (run 35037024216):

  delegate-skill.v1              6c9dc22f-...  parent=<null>  (head, correct)
  delegation-routing-request.v1  841f9c10-...  parent=<null>  (not a head)
  routing-decision.v1            223b9623-...  parent=841f9c10-...
  delegate-skill-completed.v1    3b1b8925-...  parent=6c9dc22f-...  (the branch)

RED-first, against parent 76c8658:
  tests/integration/runtime/test_chain_causal_edge_seam_omn18419.py
    3 failed, 1 passed -- the worker-command edge fails on
    `assert None == UUID(...)`; the ingress-head case is the positive control
    and passes on both sides.
  Replaying those four rows through the parent's own replay reproduces the live
  verdict exactly: hops 1 and 3 red, hops 0 and 2 green.
At this head both files are green (15 + 4 passed), the post-fix five-hop wire
shape grades 5/5 green, and the four rows as RECORDED still grade red -- the
fix does not paper over the evidence that produced the defect.

mypy --strict clean over 3044 files; `pre-commit run --all-files` green.
…the fifth chain hop

Mechanical. Adding onex.cmd.omnibase-infra.delegation-request.v1 to
node_ledger_projection_compute's subscription changes that node's
content-hashed dispatcher set, so the committed Mode-A oracle drifts by
construction: 51 new probes, one new topic-scoped dispatcher, and the existing
ledger-projection dispatchers' topic sets. Verified structurally rather than by
line count -- no contract other than node_ledger_projection_compute appears in
the diff, and the header's corpus counts move by exactly the one topic.

  before: 101 distinct subscribe topics, 923 probes, 122 dispatchers
  after:  102 distinct subscribe topics, 974 probes, 123 dispatchers

Regenerated with the harness the test names in its own failure message, not by
hand. tests/integration/runtime/test_dispatch_selection_parity.py: 10 passed.
@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Critical findings: 0
Major findings: 4
Total findings: 10
Models succeeded: glm-review

Note: Fewer than 2 reviewer models succeeded. Degraded results are informational (OMN-8468/OMN-8524) and do not block merge. Error: cli_review exit 2 (fewer than 2 models succeeded — partial/total outage)


Semantics (OMN-17492 — the model finds, thread resolution gates)

Surface Meaning Blocks merge?
Review threads Per-finding, posted by the reviewer No (informational)
Hostile Review Thread Gate Deterministic: unresolved hostile-reviewer threads exist Fails until resolved (not yet a required context)
degraded verdict Fewer than 2 models succeeded (infra) No

Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review: qwen3-review, qwen3-review-b, glm-review (OMN-8468/OMN-8524/OMN-17492)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hostile Reviewer — adversarial findings (OMN-17492)

Models succeeded: glm-review
Models failed: codex
New finding threads: 3
Deduped (already posted on this PR): 0
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.

Findings not anchored to a changed file

  • [CRITICAL] hostile-reviewer (glm-review)

    Cycle validation is incomplete: single-head topology with detached cycle passes construction | _parse_declared_topology rejects a topology only when EVERY hop names a parent ('no head at all ... is a cycle'). A topology with exactly one head and a cycle among the remaining hops (A head; B->C; C->B) passes all four refusals. The docstring's stated intent is that an ungradable topology 'must fail LOUDLY at construction rather than quietly produce a chain the replay rates on a relation it invented.' The detach

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Contextvar-first envelope identity changes the resume predicate's input | _load_handle_persist's resume path previously read envelope_id off the materialized envelope. The change consults current_dispatch_envelope() first. The surrounding comment states the resume predicate 'keys on envelope_id, NOT causation_id (spec §4.1 E1 — a redelivered input keeps its envelope_id)'. If the contextvar is bound to an envelope that differs from the direct argument (nested dispatch, reentrant dispatch, or a caller passing

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Correlation-id heuristic conflates self-seeded causation with legitimate edges | edge = None if causation == cid_uuid else causation assumes causation equals the correlation id only via the historical self-seeding fallback. If any code path legitimately seeds causation from an envelope whose id equals the correlation id (or a future path reuses that sentinel), a real edge is silently dropped and the hop is published as a head, which this PR itself classifies as a false 'chain head' assertion. The sentinel i

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Contextvar propagation across task boundaries can silently null the causal edge | _publish_worker_command reads current_dispatch_envelope() with a silent None fallback documented as 'the head' when publishing from HTTP ingress. Contextvars do not propagate into tasks spawned without an explicit context copy. If the broker's publish, retry, or the dispatch-port's invocation of this path executes in a separately spawned task, the contextvar reads None and a non-head delegation-request.v1 is published as a hea

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Silent None returns in _consumed_envelope_id defeat the audit edge without signal | _consumed_envelope_id returns None on a missing envelope_id and swallows UUID(direct) ValueError, returning None. A malformed or absent envelope identity on any outbox path now produces an event with parent_envelope_id null, asserted by the PR to mean 'chain head'. For an audit-ledger system, an unparsable identity is a distinct condition from a legitimate head and should be distinguishable in the ledger. | Evidence: try:

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Bare RuntimeError for contract failures conflicts with the node's typed-failure doctrine | The contract description and OMN-18398 semantics require typed failures naming the correlation id and context. The new parser raises plain RuntimeError for topology defects. Runtime construction errors and handler refusals now have two different error vocabularies in one node, weakening the guarantee the same file's description advertises. | Evidence: raise RuntimeError("chain-writer contract must declare a non-empty

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

Findings demoted from threads (anchor rejected)

  • [MAJOR] hostile-reviewer (glm-review)

    Contract-breaking schema change to chain_topology without compatibility analysis | chain_topology changes from a list of topic strings to a list of {topic, parent} mappings. The new parser explicitly refuses the old form. Any other reader of this contract field (scripts, canary tooling, cross-repo consumers) that assumes bare strings will fail. The diff updates the one handler test that reads the field but shows no inventory of other readers; the workflow file re-declares the topic list by hand rather than

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    No visible tests for the four topology-parsing refusals | _parse_declared_topology introduces four RuntimeError paths (non-list, non-mapping entry, duplicates, dangling parent, plus head count checks), but the diff shows no unit tests exercising them. Given the module's stated doctrine that a refusal must be proven, untested refusal paths are unverified behavior; and the reachable-cycle case cannot even be expressed as a refusal per the finding above. | Evidence: raise RuntimeError("chain-writer contract mu

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    No migration story for historically recorded causal edges under the new declaration | Existing event_ledger rows were written with the pre-fix seams: routing-request rows carry parent null, and outbox-derived rows carry correlation-seeded causation. After deployment, replaying any pre-fix correlation against the new declaration grades the branch red and flags declared non-heads that record no parent... actually the inverse: hops whose recorded parent is null but are not declared heads will now fail the edge

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 16, 2026
…ibase_infra#3610 (#9755)

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

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

* evidence(OMN-18419): self-bind OCC#9755 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
…not bare topics

Missed in the first commit: this file constructs HandlerDelegationChainLedger
with declared_chain= directly, so the declaration-shape change reached it too.
It failed CI split 2/15 on run 35043176426 with 'Expected mock to have been
awaited' -- the handler raised on the bare strings before reaching the persist
edge the test asserts on, which is the right failure and the wrong reason.

The harness declares a LINE, which is what its _observed builds; the tree cases
live beside the grading rule in tests/unit. What this file exercises is the
refusal path through the real wiring, which is indifferent to the topology's
shape.

tests/integration/nodes/node_delegation_chain_ledger_effect/: 4 passed.
…tree-replay

# Conflicts:
#	tests/fixtures/dispatch_parity/baseline-selection-v2.json

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hostile Reviewer — adversarial findings (OMN-17492)

Models succeeded: glm-review
Models failed: codex
New finding threads: 3
Deduped (already posted on this PR): 0
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.

Findings not anchored to a changed file

  • [MAJOR] hostile-reviewer (glm-review)

    Topology validation misses non-head cycles | _parse_declared_topology rejects duplicate topics, dangling parents, zero heads, and multiple heads, but it does not reject a cycle among non-head hops. A topology such as head C plus A->B->A passes all four checks: one head, no dangling parents, no duplicates. Both A and B are unreachable from the head, so any observed hop on A or B will be graded against a parent relation that can never close for a reachable chain, and the failure surfaces only at replay time w

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Causation suppression heuristic uses sentinel comparison | The new edge logic in _publish_outbox_batch suppresses the parent whenever causation equals the correlation id, which is correct for the correlation-seeded fallback it targets. But it records every other causation value unconditionally as parent_envelope_id. Per the truncated-adjacent code the fallback existed precisely because many call sites seed causation with non-envelope values; any call site seeding causation with an id that is neither a corre

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Contextvar-derived parentage applied to all pattern-B routes | _publish_worker_command now inherits parent_envelope_id from current_dispatch_envelope() for every route, not just the delegation chain. If the broker publishes a route whose worker command is not causally downstream of the currently-dispatched envelope (deferred dispatch, queued worker commands drained outside the consuming dispatch context, or a contextvar that leaked across task boundaries), the change fabricates an edge. The comment asserts

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Contextvar shadows correct direct envelope_id | _consumed_envelope_id consults the contextvar first and falls back to the envelope argument only when the contextvar is absent or carries a non-UUID envelope_id. If the contextvar holds a stale or unrelated typed envelope while the caller passes the actual consumed envelope directly, the correct attribute value is discarded in favor of the wrong contextvar value. The docstring frames the ordering as backwards compatibility but it is a precedence rule, and the

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Resume predicate identity source change is broader than stated | Replacing getattr(envelope, 'envelope_id', None) with _consumed_envelope_id(envelope) in _load_handle_persist changes incoming_envelope_id for every stateful handler whose input is the materialized dict while a dispatch contextvar is bound, not only for the delegation path. The comment in the seam test argues the fix is correct in general, but the resume predicate and any persisted recovery rows written before this change keyed on the old deri

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Linear topic scan per hop in replay | _declared_hop_for scans the declared chain for each observed hop, O(n*m). Harmless at five hops, but the function is positioned as a general grading rule ('a line is a tree whose every parent is the preceding hop') and chain lengths elsewhere may grow; the caller also reconstructs the declared topic set separately in handle() for the settle loop, so a dict is already materializable at construction time. | Evidence: for candidate in declared_chain:
    if candidate.topic

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

Findings demoted from threads (anchor rejected)

  • [MAJOR] hostile-reviewer (glm-review)

    Workflow hop list duplicates contract topology with no enforcement | The PR itself documents the risk in its own comment ('a chain graded against one and checked against the other is two declarations free to disagree') and then does exactly that: EXPECTED_LEDGER_HOPS in chain-canary.yml is a hand-maintained copy of chain_topology including order. The PR adds a cross-contract consistency test for node_ledger_projection_compute vs the chain contract but no test compares the workflow env to the contract. Five

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    No visible coverage of topology validation refusals | _parse_declared_topology adds four (really five, cycle-inclusive) refusal branches executed at handler construction. The changed test files cover replay grading and the absent-evidence integration path, but no test in the diff exercises the parser refusals: duplicate topics, dangling parent, zero heads, multiple heads, malformed entries, or the legacy bare-string topology list now rejected. A regression that silently weakens one refusal branch would go u

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    ModelDeclaredChainHop accepts arbitrary topic strings | topic is validated only by min_length=1. The whole point of the model is to reference real bus topics; a typo'd topic in contract.yaml produces a valid declaration whose observed hop can never appear, so the chain will now grade RED on a missing hop rather than failing at load, and the settle loop will burn all settle_attempts waiting for evidence that cannot exist. The old string-list validator had the same gap, but the new model is the declared autho

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

@jonahgabriel
jonahgabriel merged commit 6e41f3e into dev Sep 16, 2026
147 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-18419-chain-tree-replay branch September 16, 2026 02:31
Patel230 pushed a commit that referenced this pull request Sep 16, 2026
…e replay grades the tree it declares (#3610)

* fix(OMN-18419): the delegation chain records its causal edges, and the replay grades the tree it declares

chain-canary link 5 reported `ledger_replay_failed (4/5 links proven)` on a
chain that had run end to end. Two independent defects, one verdict.

1. The causal edge was dropped at two publish seams.

   `RuntimePatternBBroker._publish_worker_command` builds a fresh envelope for
   the route's command topic and published it as a chain HEAD every time. It
   publishes TWO hops of this chain: the head, from the HTTP ingress, and
   `onex.cmd.omnibase-infra.delegation-request.v1`, from
   `service_delegation_dispatch_port` -- which runs INSIDE the delegate-skill
   handler's dispatch, with the consumed envelope bound on the engine's
   contextvar. It now records that envelope. Absence is preserved: the ingress
   hop consumes nothing and still publishes with no parent, which is the
   checkable statement "head" the replay now checks rather than assumes.

   `MessageDispatchEngine` hands a dispatcher the JSON-safe materialization of
   the envelope and binds the typed one beside it "for transport identity (for
   example envelope_id)" -- its own words. The state_io wrapper read
   `envelope_id` as an ATTRIBUTE off that mapping, always None, so every outbox
   entry seeded its causation with the CORRELATION id via the fallback. Proven
   from the wire: the routing intent's deterministic id on run 35037024216
   re-derives as `uuid5(correlation, f"{correlation}:ModelRoutingIntent:0")`
   and not from the `delegation-request` envelope
   `f1cc9a51-ef02-40f4-b2e7-81e5b57942ed` that caused it. `_publish_outbox_batch`
   then dropped even that seed from the envelope it published. Both are fixed;
   the completion-bound sweep's deliberate self-seed still publishes no edge,
   because a correlation id is not an envelope id.

2. The chain is a TREE and the replay re-derived a LINE.

   `chain_replay._replay_one_hop` compared each hop's recorded parent against
   `observed[index - 1]`. `delegate-skill-completed` records the delegate-skill
   COMMAND as its parent -- it is a consequence of consuming that command, not
   of the routing decision that precedes it in time -- so a correct edge graded
   red. `chain_topology` is now an ordered list of hops that each declare their
   parent TOPIC (`ModelDeclaredChainHop`), and the replay grades the recorded
   edge against the envelope observed on the declared parent. Nothing about
   this canary is special-cased: a line is a tree whose every parent is the
   preceding hop. Tier 2 still reads the ORDER; tier 1 now reads the PARENT
   RELATION. A hop no declaration reaches replays RED with a stated reason
   rather than falling back to the positional guess -- the same rule tier 2
   already held.

The declaration also gains the `delegation-request.v1` hop, which was always
traversed and never declared, and `node_ledger_projection_compute` (contract
1.4.0) now records it -- that node is the only writer of `public.event_ledger`,
so without it the routing request's recorded parent named an envelope no
correlation-scoped read could resolve.

Evidence, read read-only on the .201 compose dev lane, correlation
41235987-425c-481b-b2e3-8970083ce512 (run 35037024216):

  delegate-skill.v1              6c9dc22f-...  parent=<null>  (head, correct)
  delegation-routing-request.v1  841f9c10-...  parent=<null>  (not a head)
  routing-decision.v1            223b9623-...  parent=841f9c10-...
  delegate-skill-completed.v1    3b1b8925-...  parent=6c9dc22f-...  (the branch)

RED-first, against parent 76c8658:
  tests/integration/runtime/test_chain_causal_edge_seam_omn18419.py
    3 failed, 1 passed -- the worker-command edge fails on
    `assert None == UUID(...)`; the ingress-head case is the positive control
    and passes on both sides.
  Replaying those four rows through the parent's own replay reproduces the live
  verdict exactly: hops 1 and 3 red, hops 0 and 2 green.
At this head both files are green (15 + 4 passed), the post-fix five-hop wire
shape grades 5/5 green, and the four rows as RECORDED still grade red -- the
fix does not paper over the evidence that produced the defect.

mypy --strict clean over 3044 files; `pre-commit run --all-files` green.

* test(OMN-18419): regenerate the dispatch-selection parity oracle for the fifth chain hop

Mechanical. Adding onex.cmd.omnibase-infra.delegation-request.v1 to
node_ledger_projection_compute's subscription changes that node's
content-hashed dispatcher set, so the committed Mode-A oracle drifts by
construction: 51 new probes, one new topic-scoped dispatcher, and the existing
ledger-projection dispatchers' topic sets. Verified structurally rather than by
line count -- no contract other than node_ledger_projection_compute appears in
the diff, and the header's corpus counts move by exactly the one topic.

  before: 101 distinct subscribe topics, 923 probes, 122 dispatchers
  after:  102 distinct subscribe topics, 974 probes, 123 dispatchers

Regenerated with the harness the test names in its own failure message, not by
hand. tests/integration/runtime/test_dispatch_selection_parity.py: 10 passed.

* test(OMN-18419): the refusal-path integration harness declares hops, not bare topics

Missed in the first commit: this file constructs HandlerDelegationChainLedger
with declared_chain= directly, so the declaration-shape change reached it too.
It failed CI split 2/15 on run 35043176426 with 'Expected mock to have been
awaited' -- the handler raised on the bare strings before reaching the persist
edge the test asserts on, which is the right failure and the wrong reason.

The harness declares a LINE, which is what its _observed builds; the tree cases
live beside the grading rule in tests/unit. What this file exercises is the
refusal path through the real wiring, which is indifferent to the topology's
shape.

tests/integration/nodes/node_delegation_chain_ledger_effect/: 4 passed.
Patel230 pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 16, 2026
…ibase_infra#3610 (#9755)

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

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

* evidence(OMN-18419): self-bind OCC#9755 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
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