dfx 0.1.0
Linux-based dynamic dataflow executor
Loading...
Searching...
No Matches
Graph channel verification (kind + mime-type compatibility)

This page documents the verification performed by dfx::Graph::Controller::addChannel() when dfx::Graph::Controller::isChannelVerificationEnabled() is true (default).

The goal is simple: reject connections that would make the graph internally inconsistent, even when some ports declare "any" mime-type and rely on propagation.

High-level rules

A channel can be created only if:

  • the two ports have the same dfx::Core::Kind (audio/video/text/…),
  • there exists at least one compatible mime-type between what the output can emit and what the input can accept.

"Compatible" means the intersection of the two mime-type sets is non-empty.

"any" ports and propagation

The tricky part is when ports don’t declare a fixed mime-type.

dfx uses two concepts:

  • Output port mime-type: dfx::Core::OutputPort::mimeType()
    • If it is not any, the output is fixed to that single mime-type.
    • If it is any, the output may be constrained by propagation rules and existing wiring.
  • Input port supported mime-types: dfx::Core::InputPort::supportedMimeTypes()
    • If the list has exactly one entry, the input is fixed.
    • If it has multiple entries (or is empty depending on implementation), it’s a set of accepted types.

Propagation is controlled by:

The effective rule is:

  • if the port has an explicit propagation value, use it;
  • otherwise, use the node’s value.

How "possible mime-types" are computed

Verification computes possible mime-types for each side of the prospective connection. Each result is paired with a path explanation: a list of Node.Port segments showing how that possibility was inferred.

Internally, the implementation builds:

  • possible types for the source output port (getPossibleMimeTypesForOutputPort()),
  • possible types for the destination input port (getPossibleMimeTypesForInputPort()).

Source: OutputPort possible mime-types

Let src be the output port.

  1. If src.mimeType() is not any:
    • Possible types = { src.mimeType() }
    • Path = [ "SrcNode.SrcPort" ]
  2. If src.mimeType() is any:
    • If propagation is disabled for src:
      • The algorithm inspects the node’s input ports of the same dfx::Core::Kind.
      • For each such input port:
        • If that input’s supported list has exactly one entry (fixed):
          • It contributes that fixed type with a path: [ "SrcNode.SrcPort", "SrcNode.InputPort" ]
        • Otherwise (input accepts multiple types):
          • The algorithm walks the channels connected to that input port and looks at the upstream output ports feeding it (excluding src itself), and recursively tries to infer their possible types.
          • The input port’s supported set is then merged with the upstream result:
            • if one side is empty, the other wins;
            • otherwise, the merge is an intersection.
    • If this exploration yields no constraints, the output side is treated as "can emit anything":
      • Possible types = empty set
      • Path = [ "SrcNode.SrcPort" ]

Cycles are handled by a visited-node set: recursion stops when a node is revisited.

Destination: InputPort possible mime-types

Let dst be the input port.

  1. If dst.supportedMimeTypes().size() == 1:
    • Possible types = that single type (fixed)
    • Path = [ "DstNode.DstPort" ]
  2. Otherwise (multiple accepted types):
    • If propagation is enabled for dst:
      • The algorithm inspects the node’s output ports of the same kind that are any.
      • For each such output port, it walks the channels connected to it and considers the downstream input ports (excluding dst itself), recursively computing their possible types.
      • The downstream result is then merged with dst.supportedMimeTypes() using the same merge rule: empty wins, otherwise intersection.
    • If nothing useful is discovered, the destination side stays at:
      • Possible types = dst.supportedMimeTypes()
      • Path = [ "DstNode.DstPort" ]

Cycles are handled the same way as for the source side.

Compatibility decision

After both sets are computed:

  • If the source possible set is empty → it is treated as "can emit anything" (no check needed).
  • If the destination possible set is empty → it is treated as "can receive anything" (no check needed).
  • Otherwise, the two sets must have a non-empty intersection.

The implementation checks all (srcPath, srcTypes) × (dstPath, dstTypes) combinations and produces an error for each incompatible pair.

Error messages and paths

When a conflict is found, an error is generated with a path explanation:

  • The source path is printed from the constraint origin back toward the port being connected (the implementation reverses it for readability).
  • The destination path is printed in forward order.

Error format (as emitted by the controller):

Incompatible mime-types: path ['<src path>'] transmits ['<src types>'] but ['<dst path>'] only accepts ['<dst types>']

To avoid flooding the user with hundreds of unusable messages, an optional errorLimit can cap the number of emitted errors. When reached, a final message is appended:

  • More errors have been found but output is limited to N errors

Controller behavior on failure

  • If verification is disabled, the controller accepts the channel without checks.
  • If verification fails:
    • If the caller passed an errors pointer to dfx::Graph::Controller::addChannel(), the function returns an empty id and populates *errors.
    • Otherwise, errors are logged and an exception is thrown.

Practical notes

  • This verification is intentionally conservative: if types cannot be inferred, it prefers to reject only when it can prove incompatibility, and otherwise treats "unknown" as "any".
  • The path output is meant for humans: it tells you which existing wiring created the constraint that makes a new connection invalid.