diff --git a/src/host/taint/owner.rs b/src/host/taint/owner.rs index ac7d0eb..464d4d0 100644 --- a/src/host/taint/owner.rs +++ b/src/host/taint/owner.rs @@ -300,6 +300,28 @@ fn keys_of(node: &NodeSnapshot, ctx: &OwnerCtx) -> Vec<(OwnerKey, KeyValue)> { /// ⚠️ Do this **with the §5.1 matrix data in hand**, not before: the whole /// question is how much over-exclusion the rule actually causes on a real /// graph, and that is measurable rather than arguable. +/// +/// ## Round 12 — the deferral holds, and "resolved" has a trap in it +/// +/// Codex re-examined this and agreed the deferral is defensible while +/// `evaluate()` is audit-only, and that the rule above closes the recorded path +/// without unbounding normal Pulse-emulated apps — **but only under one +/// reading of "resolves"**, and the wrong reading reintroduces the hole: +/// +/// - ✅ "Resolved" must mean **an unambiguous Client that yields +/// `Some(pipewire.sec.pid)`**, taken *before* the pipewire-pulse suppression +/// step. A Pulse app then still has the daemon's protected PID as +/// provenance, even though that value is omitted from the bridge keys, so it +/// stays bounded and the eligible half survives. +/// - ❌ **Do not** implement it as "a unique Client object exists". A unique +/// Client with `sec_pid = None` would satisfy that test while providing no +/// protected identity at all, leaving exactly the self-claimed-PID hole this +/// rule is meant to close. +/// +/// So the matrix needs five Client cases, not two: **absent**, **ambiguous**, +/// **unique but pid-less**, **resolved-native**, and +/// **resolved-to-pipewire-pulse**. The third is the one that distinguishes the +/// two readings, and it is the row a two-case matrix would silently skip. pub fn owner_is_bounded(node: &NodeSnapshot, ctx: &OwnerCtx) -> bool { keys_of(node, ctx) .iter()