Repository navigation
fix(OMN-18419): the delegation chain records its causal edges, and the replay grades the tree it declares - #3610
Conversation
…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.
|
| 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)
There was a problem hiding this comment.
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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks while hostile-reviewer threads are unresolved (OMN-17492).
…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
There was a problem hiding this comment.
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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks 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.topicResolve this thread when addressed — the
Hostile Review Thread Gateblocks 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 Gateblocks 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 Gateblocks 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 Gateblocks while hostile-reviewer threads are unresolved (OMN-17492).
…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.
…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>
Closes OMN-18419.
chain-canary link 5 reported
ledger_replay_failed (4/5 links proven)on run35037024216 (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, correlation41235987-425c-481b-b2e3-8970083ce512(that run's own):public.ledger_chaingraded hops 1 and 3replay_green = false, tier-2passon all four.
1. The causal edge was dropped at two publish seams
The pattern-B worker command.
RuntimePatternBBroker._publish_worker_commandbuilds 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, fromservice_delegation_dispatch_port— which runs inside the delegate-skillhandler'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.
MessageDispatchEnginehands a dispatcher theJSON-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_idas an attribute off that mapping, which isalways
None, so every outbox entry seeded its causation with the correlationid 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 thedelegation-requestenvelopef1cc9a51-ef02-40f4-b2e7-81e5b57942edread offthe dev-lane broker that actually caused it.
_publish_outbox_batchthendropped 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_hopcompared each hop's recorded parent againstobserved[index - 1].delegate-skill-completedrecords the delegate-skillcommand 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_topologyis now an ordered list of hops that each declare their parenttopic (
ModelDeclaredChainHop), and the replay grades the recorded edge againstthe 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:
falling back to the positional guess. A check that could not run has not
passed — the rule tier 2 already held.
green by construction, so that case went unreported.
The declaration also gains the
delegation-request.v1hop, which was alwaystraversed and never declared, and
node_ledger_projection_compute(contract1.4.0) records it. That node is the only writer of
public.event_ledger, sowithout 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:Replaying the four rows above through the parent's own
assemble_replay_and_verifyreproduces the live verdict exactly:
At this head:
tests/unit/nodes/node_delegation_chain_ledger_effect/test_chain_replay.py15 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 filespre-commit run --all-files— green (the topic enum regeneration is included)outbox and completion-bound suites, the OMN-18389 broker seam file, the ledger
projection contract and dispatch-reachability suites — all green
compose-devlab pass on merge isthe 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