# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-treat-trace-span-parentage-as-authoritative-evidence-of-causal-ordering.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-treat-trace-span-parentage-as-authoritative-evidence-of-causal-ordering.warrant.json
# Markdown SHA-256:   f676ab0b3c6ba17a21bb33c01acbb4dcc32616a83bb6bd98866a4386d05c73bf
# Code AST SHA-256:   fb824750789b8dec51497b390cbc3c0d51e83bffc114205acc579f3c983ebb67
# Verified Classes:   CausalEdgeInversion
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class CausalEdgeInversion(RuntimeError):
    """Raised when span parentage contradicts the mutation log's version order."""
    pass


def build_causal_edge(parent_span, child_span, mutation_log):
    """
    Decide whether parent_span -> child_span is a strong causal edge
    or merely a weak spawn edge. The mutation log is the ordering authority.
    """
    if child_span.parent_span_id != parent_span.span_id:
        raise CausalEdgeInversion(
            f"span {child_span.span_id} is not a child of {parent_span.span_id}"
        )

    # A child that starts after its parent ended crossed a queueing boundary.
    deferred = child_span.start_time > parent_span.end_time

    if deferred:
        return {"edge": "weak_spawn", "reason": "deferred_past_parent_termination"}

    # Even if not deferred, the mutation log must not invert the order.
    child_version = mutation_log.version_at(child_span.end_time)
    parent_read_version = mutation_log.version_observed_by(parent_span.span_id)

    if child_version > parent_read_version and child_span.reads_memory:
        # The child claims to have read state that only landed after it.
        raise CausalEdgeInversion(
            f"version inversion: child {child_span.span_id} read v:{parent_read_version} "
            f"but mutation log shows v:{child_version} at its completion; "
            f"parentage edge is spurious for ordering"
        )

    return {"edge": "strong_causal", "reason": "synchronous_and_version_consistent"}
