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.
- If src.mimeType() is not any:
- Possible types = { src.mimeType() }
- Path = [ "SrcNode.SrcPort" ]
- 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.
- If dst.supportedMimeTypes().size() == 1:
- Possible types = that single type (fixed)
- Path = [ "DstNode.DstPort" ]
- 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.