docs: phase 5 matrix PASSED (13/13) — design round 10, results run 2

The §5.1 dry-run audit gate passes. All 13 rows completed with a non-empty
eligible half in every one, and O5 is re-measured on the fixed graph: worst
recompute 67 µs across every run, 10 µs mean under deliberate churn, busy
fraction 0.0006, readiness 1-2 ms with 18 binds against a 2000 ms budget.

Round 10's finding, and it is the third of exactly the same shape: the
pipewire-pulse PID derivation required a SINGLE repeated pipewire.sec.pid.
WirePlumber repeats one too (two Clients, both sec_pid 1747), so the derivation
returned None permanently on a stock desktop, key 4's suppression never fired,
and every Pulse-emulated node fused into one owner. Row 1's CLEAN control
forwarder and Firefox were both excluded. Fixed in pixelpass 91c4ded by deleting
the heuristic: probe every distinct sec_pid and let /proc/<pid>/comm decide.

Three measured rounds now, all at the observation boundary, none in the
architecture -- and all three were fail-closed and silent, caught only because
§5.1 requires asserting what must remain ELIGIBLE. An exclusion-only checklist
would have passed every one of these builds.

Rows 4-6 are closed through peerspeak's REAL tagging sites (call, mpv, notify,
plus clip) with a hand-launched mpv staying eligible, so the cross-repo contract
is proven end to end on live nodes. Row 10 covers the full sticky lifecycle
including retirement; row 11 is provably non-vacuous (the recycled
node.link-group came back byte-identical and did not inherit taint).

Recorded and NOT claimed as passes: substitutions in rows 8, 9 and 13, and two
reporting-only findings (the audit's sticky flag is uninformative; a bridge's
named key is lost when a leg reappears under a new serial).

Phase 6 remains blocked by F11-1, phases 0b/0c/0d and the Stereo Mix design
call -- this file removes one gate, not all of them.
This commit is contained in:
2026-07-26 02:59:43 -04:00
parent b68fca689e
commit bf908adbf0
3 changed files with 519 additions and 271 deletions
+71 -10
View File
@@ -1,11 +1,14 @@
# Design v3: whole-desktop screen-share audio without self-echo
**Status:** 🟠 **v3.6 — round 9, opened by a second MEASURED finding, this time from a live
audit run of the *fixed* observer.** v3.4's architecture is still unchanged and converged.
Round 8 revised the **observation boundary** (§6.7); round 9 revises what stickiness is
allowed to remember (new §6.8). Both were found by running code, not by reading it.
**Date:** 2026-07-25 (v1: 07-19 · v2: 07-20 · Option C 07-20 · v3.1 r4 · v3.2 r5 · v3.3 r6 ·
v3.4 r7 · v3.5 r8 · v3.6 r9)
**Status:** 🟢 **v3.7 — round 10: the §5.1 matrix PASSED in full and the architecture is
unchanged for the third consecutive measured round.** Round 8 revised the **observation
boundary** (§6.7), round 9 revised what stickiness may remember (§6.8), and round 10 deletes
the pipewire-pulse PID **derivation heuristic** (§6.1.2) after measuring that WirePlumber
repeats a `sec_pid` too — which had switched key 4's suppression off permanently. All three
were found by running code, not by reading it, and all three were at the *observation*
boundary rather than in the design.
**Date:** 2026-07-26 (v1: 07-19 · v2: 07-20 · Option C 07-20 · v3.1 r4 · v3.2 r5 · v3.3 r6 ·
v3.4 r7 · v3.5 r8 · v3.6 r9 · v3.7 r10)
**Origin:** Joe's suggestion — "whitelist all audio except audio coming from peerspeak."
**Scope:** a new capture mode in pixelpass (`src/host/pipeline.rs`, `src/host/audio.rs`),
playback tagging + AEC-identity export + teardown-ordering invariants in peerspeak.
@@ -502,11 +505,47 @@ derive this itself; it cannot assume a value, and peerspeak can supply only a *h
- Read `pipewire.sec.pid` from the **Client** objects of Pulse-emulated streams. Measured:
it is `2541` for Firefox, Steam, KDE Connect, sunshine and libcanberra alike, while each
node's own `application.process.id` differs (Firefox `11114`, sunshine `4119`).
- Require a **single consistent** value across those clients, and validate it by reading
- ~~Require a **single consistent** value across those clients~~ and validate it by reading
`/proc/<pid>/comm` (or cmdline) and confirming it is `pipewire-pulse`.
- "This PID owns implausibly many unrelated streams" is a **diagnostic**, never
correctness logic.
> #### 🔴 Round 10 (MEASURED, phase-5 run 2): "a repeated `sec_pid`" does not identify pulse
>
> The struck rule above was implemented as *the single `sec_pid` shared by two or more
> Clients*, on the reasoning that native clients each carry their own distinct PID so only the
> Pulse shim repeats a value. **Measured on this host: WirePlumber repeats one too** — it holds
> two Clients, `WirePlumber` and `WirePlumber [export]`, both `sec_pid` 1747. Two values
> repeated, "single consistent" was unsatisfiable, and the derivation returned `None`
> **permanently, on a stock desktop**.
>
> The consequence was not a missing optimisation. With the daemon PID unknown the key-4
> exception never fires, every Pulse-emulated node fuses into one owner, and the result is the
> machine-wide over-exclusion cascade of phase 5's F2 — reached again from a new cause, and
> caught again only by the §5.1 requirement to assert the **eligible** half of a row.
>
> The rule failed in both directions, so the repetition test is **deleted** rather than
> tightened:
>
> - **False ambiguity** — any second process holding two Clients defeats it, and WirePlumber
> always does.
> - **False absence** — a session in which pipewire-pulse holds exactly one Client (one Pulse
> app running) repeats nothing at all, so the candidate is never even considered.
>
> `comm` was always the authoritative check; repetition was a heuristic standing in front of it,
> and what it actually encoded was an assumption about *other* processes' Client counts.
> **The rule is now: every distinct `pipewire.sec.pid` is a candidate; the daemon is the unique
> one whose `/proc/<pid>/comm` is exactly `pipewire-pulse`.** Zero matches ⇒ `None` (nothing we
> can prove to suppress). **Several** matches ⇒ also `None`: two live pipewire-pulse daemons (a
> nested or sandboxed session) cannot both be suppressed by a single `Option<u32>`, and failing
> closed there lands on the over-exclusion side, consistent with the failure-mode paragraph
> below. Suppressing a *set* of daemon PIDs is the real answer if a multi-daemon host ever turns
> up; it is out of v1 and recorded rather than silently approximated.
>
> The lesson generalises past this key: **a property of the objects we are trying to identify is
> evidence; a property of everyone else's object count is a guess.** The `/proc` read was already
> there and already authoritative — the heuristic in front of it only added a way to be wrong.
Failure modes: if pixelpass fails to identify the real pipewire-pulse PID, the result is
broad **over-exclusion** (annoying, safe). If it wrongly suppresses a genuine app PID, the
result is over-exclusion **for that app** — safe *only* because unresolved ancestry is
@@ -1143,9 +1182,28 @@ Both reviewers agree on all seven. Recorded as decided; reopen only with new evi
- **D7 — no materially simpler design exists** that still meets Joe's ask. The available
simplification is to *narrow v1 scope*, not to change architecture. ✅
## 14. Readiness — 🟠 v3.6 (round 9): architecture converged; observation boundary and sticky provenance REVISED.
## 14. Readiness — 🟢 v3.7 (round 10): the §5.1 matrix PASSED; architecture unchanged.
**Round 9 (2026-07-25, same day).** Phase 3r shipped §6.7 and the audit was re-run
**Round 10 (2026-07-26).** The phase-5 matrix ran in full and **passed all 13 rows with a
non-empty eligible half in every one** — results in
`screenshare-audio-exclusion-phase5-results.md`. It also found a third measured defect on
first contact, and once again the failure was fail-closed and *silent*, exposed only by the
requirement to assert what must remain **eligible**: §6.1.2's pulse-PID derivation returned
`None` permanently on this host, so key 4 fused every Pulse-emulated node into one owner.
| | verdict |
| --- | --- |
| Architecture — Option C, taint as a graph property, owner-key union, sticky taint, AEC identity state machine | **unchanged, three times vindicated** |
| §6.1.2 | **revised** — the derivation heuristic is deleted; `comm` alone decides |
| §5.1 matrix | **PASSED** — 13/13, incl. the full sticky lifecycle (row 10) and provable identifier recycling (row 11) |
| O5 | **closed on the real graph** — worst recompute 67 µs, churn mean 10 µs, busy fraction 0.0006 |
| Phase 5 | **machinery unchanged and correct** — three real defects caught on first contact with the live graph, none of them in the engine |
| Phase 6 | **still blocked** — by F11-1, phases 0b/0c/0d, and the "Stereo Mix" design call, *not* by this matrix |
Three rows passed with recorded substitutions (8 EasyEffects, 9 Firefox's own mic/monitor
paths, 13 a real `Audio/Duplex` device) and the third-party samples stay owed.
**Round 9 (2026-07-25).** Phase 3r shipped §6.7 and the audit was re-run
immediately; it found a *second* measured defect within minutes — a permanent sticky taint
on a hardware sink (§6.8). Both rounds share a shape worth naming: **the architecture was
right and the instrumentation was wrong**, and only running the code against a live daemon
@@ -1205,7 +1263,10 @@ How the blockers closed:
| **9** | `device_props` tested for one live *Device* rather than one live *global* on the id (Codex, certain) | **fixed** in phase 3r — stale `session_device` on a contested id is an echo path |
| **9** | `device.api` corroborated by presence, so `v4l2` under an ALSA factory passed (Codex) | **fixed** in phase 3r — the API must equal the allowlist's own |
| **9** | hardware playback-to-capture ("Stereo Mix") defeats the `session_device` classifier (Codex, P1 worth checking) | **OPEN — design decision owed**, §6.8; pre-existing, needs ALSA control inspection |
| **9** | the 2 s readiness budget has no calibration argument (Codex) | **OPEN — measurement owed**, §6.8; ~3 ms observed on this host |
| **9** | the 2 s readiness budget has no calibration argument (Codex) | **OPEN — measurement owed**, §6.8; 12 ms observed on this host with 18 binds (phase-5 run 2) |
| **10** | **the pulse-PID derivation required a *single* repeated `sec_pid`; WirePlumber repeats one too, so it returned `None` permanently and key 4's suppression never fired (measured, phase-5 run 2)** | **fixed** — §6.1.2 round-10 box: probe every distinct `sec_pid`, let `/proc/<pid>/comm` decide |
| **10** | the audit's `sticky` flag means "is in the remembered set", so it is true for nearly every tainted node and does not answer "excluded only because remembered" | **OPEN — reporting only**; the evidence-only pass §6.8 already computes what is needed |
| **10** | a bridge's named key is lost when a leg reappears under a new serial (sticky `reason_for` falls back to keyless, and `raise` will not replace a same-rank reason) | **OPEN — reporting only**; verdict unaffected |
### v1 scope — agreed