Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/chain-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,19 @@ jobs:
# a chain short by a hop must not look complete simply because every
# row present was well-formed. These are the ordered topics one
# delegation traverses, per the node contracts that publish them.
#
# OMN-18419 adds delegation-request.v1, the pattern-B worker command
# the runtime's delegation dispatch port publishes while consuming
# the delegate-skill command. It was always traversed and never
# declared, so the routing request's recorded causal parent named an
# envelope that was not in the chain. These five must stay equal to
# node_delegation_chain_ledger_effect's `chain_topology` topics, in
# order: that contract is the writer's authority and this list is the
# reader's, and a chain graded against one and checked against the
# other is two declarations free to disagree.
EXPECTED_LEDGER_HOPS: >-
onex.cmd.omnimarket.delegate-skill.v1,
onex.cmd.omnibase-infra.delegation-request.v1,
onex.cmd.omnibase-infra.delegation-routing-request.v1,
onex.evt.omnibase-infra.routing-decision.v1,
onex.evt.omnimarket.delegate-skill-completed.v1
Expand Down
9 changes: 9 additions & 0 deletions scripts/check_subscribe_wiring_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@
"onex.cmd.omnimarket.delegate-skill.v1": "Published by omnimarket node_delegate_skill_orchestrator (command_topic, contract.yaml:220), cross-repo | owner: jonah | expiry: 2026-12-01",
"onex.cmd.omnibase-infra.delegation-routing-request.v1": "Published by omnimarket node_delegation_orchestrator (contract.yaml:148), cross-repo | owner: jonah | expiry: 2026-12-01",
"onex.evt.omnibase-infra.routing-decision.v1": "Published by omnimarket node_delegation_routing_reducer as its success terminal (contract.yaml:87), cross-repo | owner: jonah | expiry: 2026-12-01",
# OMN-18419: the fifth delegation-chain hop. Unlike the four above it is
# published from THIS repository, but not by a contract-declared publisher
# a scan of `publish_topics` can see: RuntimePatternBBroker publishes it as
# `route.command_topic`, and the route is DISCOVERED at runtime from
# omnimarket node_delegation_orchestrator's own subscribe_topics
# (service_delegation_dispatch_port.py). The topic string appears in no
# publish_topics list anywhere, which is why it needs an entry here rather
# than a contract fix.
"onex.cmd.omnibase-infra.delegation-request.v1": "Published by RuntimePatternBBroker as the runtime-discovered route.command_topic for node_delegation_orchestrator (service_delegation_dispatch_port.py), never declared in a publish_topics list | owner: jonah | expiry: 2026-12-01",
}

# ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class EnumOmnibaseInfraTopic(str, Enum):
CMD_CODING_AGENT_INVOKE_V1 = "onex.cmd.omnibase-infra.coding-agent-invoke.v1" # onex.cmd.omnibase-infra.coding-agent-invoke.v1
CMD_CODING_AGENT_WORKSPACE_VALIDATE_V1 = "onex.cmd.omnibase-infra.coding-agent-workspace-validate.v1" # onex.cmd.omnibase-infra.coding-agent-workspace-validate.v1
CMD_CONSUMER_RESTART_V1 = "onex.cmd.omnibase-infra.consumer-restart.v1" # onex.cmd.omnibase-infra.consumer-restart.v1
CMD_DELEGATION_REQUEST_V1 = "onex.cmd.omnibase-infra.delegation-request.v1" # onex.cmd.omnibase-infra.delegation-request.v1
CMD_DELEGATION_ROUTING_REQUEST_V1 = "onex.cmd.omnibase-infra.delegation-routing-request.v1" # onex.cmd.omnibase-infra.delegation-routing-request.v1
CMD_FAULT_INJECT_FIXTURE_V1 = "onex.cmd.omnibase-infra.fault-inject-fixture.v1" # onex.cmd.omnibase-infra.fault-inject-fixture.v1
CMD_GATEWAY_ATTACH_REQUEST_V1 = "onex.cmd.omnibase-infra.gateway-attach-request.v1" # onex.cmd.omnibase-infra.gateway-attach-request.v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,36 @@
WHAT "REPLAY" MEANS HERE, STATED PLAINLY
----------------------------------------
It is a re-derivation of the chain's CAUSAL LINKAGE from the recorded
envelopes, compared against what was recorded. For each hop after the head,
the expected parent envelope id is recomputed from the preceding observed hop
and compared to the parent the envelope actually carries; the correlation id
on each hop is compared to the chain's own. A hop replays green only when both
re-derivations reproduce.
envelopes, compared against what was recorded. For each hop, the expected
parent is resolved from the DECLARED topology -- the hop whose consumption the
contract says causes this one -- and the envelope id observed on that declared
parent topic is compared to the parent the envelope actually carries; the
correlation id on each hop is compared to the chain's own. A hop replays green
only when both re-derivations reproduce.

THE DECLARED TOPOLOGY IS A TREE, AND THAT IS NOT A DETAIL (OMN-18419)
--------------------------------------------------------------------
Until OMN-18419 the expected parent was recomputed from ``observed[index - 1]``
-- the hop that happened to precede this one in time. That is correct for a
LINE and wrong for anything else, and the chain this module grades is a TREE.
Measured read-only on the .201 compose dev lane, correlation
``41235987-425c-481b-b2e3-8970083ce512`` (chain-canary run 35037024216):
``delegate-skill-completed`` records the ``delegate-skill`` COMMAND envelope as
its parent, because it is a consequence of consuming that command and not of
the routing decision that preceded it in time. The recorded edge was right; the
positional re-derivation was wrong; a correct chain graded red.

So the parent relation is now DECLARED, one entry per hop
(``ModelDeclaredChainHop``), and the replay grades each recorded edge against
the hop the declaration names. Nothing about this particular canary is
special-cased: a line is a tree whose every parent is the preceding hop, and it
grades identically under both readings.

A consequence worth stating because it changes a verdict: the replay now needs
the declaration. With no declaration reaching a hop there is nothing to
re-derive FROM, so that hop replays RED with a stated reason -- it does not
fall back to the positional guess and it does not report green. This is the
same rule tier 2 already held: a check that could not run has not passed.

It is deliberately NOT a re-execution of the delegation's inference. One hop of
this chain calls a language model, which is not a deterministic function, so a
Expand Down Expand Up @@ -63,6 +88,9 @@
from omnibase_infra.nodes.node_delegation_chain_ledger_effect.models.enum_tier_two_verdict import (
EnumTierTwoVerdict,
)
from omnibase_infra.nodes.node_delegation_chain_ledger_effect.models.model_declared_chain_hop import (
ModelDeclaredChainHop,
)
from omnibase_infra.nodes.node_delegation_chain_ledger_effect.models.model_ledger_chain_row import (
ModelLedgerChainRow,
)
Expand All @@ -75,17 +103,41 @@
"SKIP is not PASS"
)

_NO_DECLARED_PARENT_DETAIL = (
"no declared chain topology names this hop, so there is no parent "
"relation to re-derive the causal edge from; a check that could not run "
"has not passed"
)


def _declared_hop_for(
topic: str, declared_chain: Sequence[ModelDeclaredChainHop]
) -> ModelDeclaredChainHop | None:
"""Return the declaration for ``topic``, or None when none names it.

Resolved by TOPIC, not by position. Position is tier 2's question ("is the
observed chain the declared chain?"), and answering it twice from two
places would let the two tiers disagree about what a hop IS. Tier 1 asks
only whether the recorded causal edge closes, which is a statement about
this hop's identity and not about where it sits.
"""
for candidate in declared_chain:
if candidate.topic == topic:
return candidate
return None


def _replay_one_hop(
index: int,
hop: ModelObservedHop,
previous: ModelObservedHop | None,
observed: Sequence[ModelObservedHop],
declared_chain: Sequence[ModelDeclaredChainHop],
correlation_id: UUID,
) -> tuple[bool, str]:
"""Re-derive this hop's linkage and compare it to what was recorded.

Returns ``(replay_green, detail)``. ``detail`` is EMPTY exactly when the
replay was green — a green carrying an explanation would suggest the check
replay was green -- a green carrying an explanation would suggest the check
was hedged.
"""
if hop.correlation_id is not None and hop.correlation_id != correlation_id:
Expand All @@ -98,18 +150,61 @@ def _replay_one_hop(
),
)

if previous is None:
# The head of the chain has no predecessor. There is nothing to
# re-derive, and nothing is claimed beyond the two checks above.
declared = _declared_hop_for(hop.topic, declared_chain)
if declared is None:
return False, _NO_DECLARED_PARENT_DETAIL

if declared.parent is None:
# The declaration says this hop is the chain HEAD. That is a claim with
# a falsifier, not a free pass: a head that records a parent is either a
# mis-declared topology or a hop that is not actually the head.
if hop.parent_envelope_id is not None:
return (
False,
(
f"the declared topology makes {hop.topic!r} the chain head, "
f"but this hop records parent {hop.parent_envelope_id!r} — "
"a head has no cause to point at"
),
)
return True, ""

expected_parent = previous.envelope_id
if hop.parent_envelope_id != expected_parent:
# A redelivery is projected as a second row carrying the SAME deterministic
# envelope id, so the declared parent topic can legitimately appear more
# than once. Matching against ANY of them is not a loosening: the edge is
# checked against a concrete observed envelope id either way.
candidates = tuple(
candidate.envelope_id
for candidate in observed
if candidate.topic == declared.parent
)
if not candidates:
return (
False,
(
f"the declared parent of {hop.topic!r} is {declared.parent!r}, "
"which was not observed on this correlation — the causal edge "
"cannot be re-derived from evidence that is not there"
),
)

if hop.parent_envelope_id is None:
return (
False,
(
f"this hop records no parent, which is the checkable statement "
f"that it is a chain HEAD, but the declared topology says "
f"{hop.topic!r} is caused by {declared.parent!r} "
f"(observed as {candidates[0]!r}) — the causal edge does not close"
),
)

if hop.parent_envelope_id not in candidates:
return (
False,
(
f"parent re-derives to {expected_parent!r} from the preceding "
f"hop {previous.topic!r}, but this hop records "
f"the declared parent {declared.parent!r} was observed as "
f"{list(candidates)!r}, but this hop records "
f"{hop.parent_envelope_id!r} — the causal link does not close"
),
)
Expand All @@ -120,7 +215,7 @@ def _replay_one_hop(
def _verify_one_hop(
index: int,
hop: ModelObservedHop,
declared_chain: Sequence[str],
declared_chain: Sequence[ModelDeclaredChainHop],
) -> tuple[EnumTierTwoVerdict, str]:
"""Tier 2: does this observation match the DECLARED topology at this position?

Expand All @@ -131,7 +226,7 @@ def _verify_one_hop(
if index >= len(declared_chain):
return EnumTierTwoVerdict.SKIP, _NO_DECLARATION_DETAIL

expected_topic = declared_chain[index]
expected_topic = declared_chain[index].topic
if hop.topic == expected_topic:
return EnumTierTwoVerdict.PASS, ""

Expand All @@ -147,30 +242,32 @@ def _verify_one_hop(
def assemble_replay_and_verify(
correlation_id: UUID,
observed: Sequence[ModelObservedHop],
declared_chain: Sequence[str],
declared_chain: Sequence[ModelDeclaredChainHop],
) -> tuple[ModelLedgerChainRow, ...]:
"""Turn observed envelopes into replayed, tier-2-verified ledger rows.

``observed`` must already be in the order the envelopes were seen; this
function does not reorder it, because the observed order IS the evidence
and sorting it would erase a transposition that tier 2 exists to catch.

``declared_chain`` is the tier-2 authority: the ordered topics the node
contracts say a delegation traverses. An EMPTY declaration is legitimate
and yields SKIP on every row — the canary then reports VERIFIER_SKIPPED,
which is red. It is never treated as "nothing to check, therefore fine".
``declared_chain`` is the authority for BOTH tiers: the ordered hops the
node contracts say a delegation traverses, each naming the declared topic
that causes it. Tier 2 reads the ORDER; tier 1 reads the PARENT RELATION.
An EMPTY declaration is legitimate and yields SKIP on every tier-2 row and
a stated-reason RED on every tier-1 row — the canary then reports
VERIFIER_SKIPPED, which is red. It is never treated as "nothing to check,
therefore fine".

An empty ``observed`` returns zero rows rather than any synthetic row. The
canary computes ``all(row.replay_green for row in rows)``, which is
vacuously TRUE over an empty sequence, so it guards on the row count
separately; a single fabricated green row here would defeat that guard.
"""
rows: list[ModelLedgerChainRow] = []
previous: ModelObservedHop | None = None

for index, hop in enumerate(observed):
replay_green, replay_detail = _replay_one_hop(
index, hop, previous, correlation_id
index, hop, observed, declared_chain, correlation_id
)
verdict, verifier_detail = _verify_one_hop(index, hop, declared_chain)

Expand All @@ -188,7 +285,6 @@ def assemble_replay_and_verify(
verifier_detail=verifier_detail,
)
)
previous = hop

return tuple(rows)

Expand Down
Loading
Loading