fix(host): close unsupported fanout gates

This commit is contained in:
2026-08-21 19:45:18 -04:00
parent 6be07ef706
commit 956534f63c
7 changed files with 427 additions and 20 deletions
+36 -8
View File
@@ -201,9 +201,15 @@ pub enum Reason {
/// A `port.exclusive` port — fan-out will be refused (v3.4 §6.2). Local
/// to the node; does not propagate.
PortExclusive,
/// An encoded/passthrough stream — a second link would corrupt it.
/// No usable configured Format param has arrived. Fan-out cannot prove a
/// second link is safe. Local to the node; does not propagate.
FormatUnknown,
/// An encoded stream — a second raw-audio link would refuse or corrupt.
/// Local to the node; does not propagate.
Passthrough,
Encoded,
/// An IEC958/S/PDIF passthrough stream. Local to the node; does not
/// propagate.
Iec958Passthrough,
}
impl Reason {
@@ -219,10 +225,25 @@ impl Reason {
Self::UnresolvedOwner => "unresolved-owner",
Self::GraphNotReady => "graph-not-ready",
Self::PortExclusive => "port-exclusive",
Self::Passthrough => "passthrough",
Self::FormatUnknown => "format-unknown",
Self::Encoded => "encoded",
Self::Iec958Passthrough => "iec958-passthrough",
}
}
/// A clean, otherwise-eligible stream whose known format/port shape this
/// fan-out mode cannot link safely. These reasons are user-visible
/// `stream_unsupported` statuses; taint roots and observation gating are
/// intentional exclusions, not failures. `FormatUnknown` is deliberately
/// omitted because the initial Node-info callback can precede the Format
/// reply; reporting that transient would produce a false warning.
pub(super) fn reports_stream_unsupported(self) -> bool {
matches!(
self,
Self::PortExclusive | Self::Encoded | Self::Iec958Passthrough
)
}
/// Lower wins. A node can acquire taint several ways in one recompute
/// and the reported reason must not depend on traversal order, or the
/// audit output is unstable and the fixture tests are flaky. Explicit
@@ -241,7 +262,9 @@ impl Reason {
// because it is only consulted for untainted candidates.
Self::GraphNotReady => 8,
Self::PortExclusive => 9,
Self::Passthrough => 10,
Self::FormatUnknown => 10,
Self::Encoded => 11,
Self::Iec958Passthrough => 12,
}
}
@@ -1057,13 +1080,18 @@ fn build_decisions(
/// clean. These do not propagate — an exclusive-port stream is unlinkable,
/// not hazardous.
fn local_exclusion(snapshot: &GraphSnapshot, node: &NodeSnapshot) -> Option<Reason> {
if node.props.passthrough {
return Some(Reason::Passthrough);
}
if snapshot.ports_of(node.id).any(|port| port.exclusive) {
return Some(Reason::PortExclusive);
}
None
if node.props.passthrough {
return Some(Reason::Iec958Passthrough);
}
match node.props.stream_format {
snapshot::StreamFormat::Raw => None,
snapshot::StreamFormat::Unknown => Some(Reason::FormatUnknown),
snapshot::StreamFormat::Encoded => Some(Reason::Encoded),
snapshot::StreamFormat::Iec958 => Some(Reason::Iec958Passthrough),
}
}
/// Sticky bookkeeping for the next recompute: every tainted owner, with